mocha-qase-reporter 1.0.0-beta.4 → 1.0.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
@@ -2,10 +2,10 @@
2
2
 
3
3
  Publish results simple and easy.
4
4
 
5
- To install the latest beta version, run:
5
+ To install the latest version, run:
6
6
 
7
7
  ```sh
8
- npm install -D mocha-qase-reporter@beta
8
+ npm install -D mocha-qase-reporter
9
9
  ```
10
10
 
11
11
  ## Getting started
@@ -23,15 +23,15 @@ parameterize your tests.
23
23
  For example:
24
24
 
25
25
  ```typescript
26
+ import { qase } from 'mocha-qase-reporter/mocha';
27
+
26
28
  describe('My First Test', () => {
27
- it('Several ids', () => {
28
- this.qaseId(1);
29
+ it(qase(1,'Several ids'), () => {;
29
30
  expect(true).to.equal(true);
30
31
  });
31
32
 
32
33
  // a test can check multiple test cases
33
- it('Correct test', () => {
34
- this.qaseId([2, 3]);
34
+ it(qase([2,3],'Correct test'), () => {
35
35
  expect(true).to.equal(true);
36
36
  });
37
37
 
@@ -89,7 +89,7 @@ QASE_MODE=testops mocha --parallel
89
89
  After the tests are finished, you can complete the run:
90
90
 
91
91
  ```bash
92
- qli testops run complete --project DEMO --token token --run $(echo $QASE_TESTOPS_RUN_ID)
92
+ qli testops run complete --project DEMO --token token --id $(echo $QASE_TESTOPS_RUN_ID)
93
93
  ```
94
94
 
95
95
  ## Configuration
package/changelog.md CHANGED
@@ -1,3 +1,17 @@
1
+ # qase-mocha@1.0.0
2
+
3
+ ## What's new
4
+
5
+ Major release of the Cypress reporter package
6
+
7
+ # qase-mocha@1.0.0-beta.5
8
+
9
+ ## What's new
10
+
11
+ - Added a `qase` function to allow specifying QaseID for tests.
12
+ - Marked the old syntax for QaseID as deprecated.
13
+ - Implemented functionality to capture console logs and include them as attachments in tests.
14
+
1
15
  # qase-mocha@1.0.0-beta.4
2
16
 
3
17
  ## What's new
@@ -0,0 +1,12 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { Writable } from 'stream';
4
+ export interface TestOutput {
5
+ stdout: string;
6
+ stderr: string;
7
+ }
8
+ export declare class StreamInterceptor extends Writable {
9
+ private readonly onWrite;
10
+ constructor(onWriteCallback: (data: string) => void);
11
+ _write(chunk: any, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
12
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StreamInterceptor = void 0;
4
+ const stream_1 = require("stream");
5
+ class StreamInterceptor extends stream_1.Writable {
6
+ onWrite;
7
+ constructor(onWriteCallback) {
8
+ super();
9
+ this.onWrite = onWriteCallback;
10
+ }
11
+ _write(chunk, _encoding, callback) {
12
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call
13
+ this.onWrite(chunk.toString());
14
+ callback();
15
+ }
16
+ }
17
+ exports.StreamInterceptor = StreamInterceptor;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Set IDs for the test case
3
+ *
4
+ * @param caseId
5
+ * @param name
6
+ * @example
7
+ * it(qase(1, 'test'), function() => {
8
+ * // test code
9
+ * });
10
+ * @returns {string}
11
+ */
12
+ export declare const qase: (caseId: number | string | number[] | string[], name: string) => string;
package/dist/mocha.js ADDED
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.qase = void 0;
4
+ /**
5
+ * Set IDs for the test case
6
+ *
7
+ * @param caseId
8
+ * @param name
9
+ * @example
10
+ * it(qase(1, 'test'), function() => {
11
+ * // test code
12
+ * });
13
+ * @returns {string}
14
+ */
15
+ const qase = (caseId, name) => {
16
+ const caseIds = Array.isArray(caseId) ? caseId : [caseId];
17
+ const ids = [];
18
+ for (const id of caseIds) {
19
+ if (typeof id === 'number') {
20
+ ids.push(id);
21
+ continue;
22
+ }
23
+ const parsedId = parseInt(id);
24
+ if (!isNaN(parsedId)) {
25
+ ids.push(parsedId);
26
+ continue;
27
+ }
28
+ console.log(`qase: qase ID ${id} should be a number`);
29
+ }
30
+ return `${name} (Qase ID: ${caseIds.join(',')})`;
31
+ };
32
+ exports.qase = qase;
@@ -3,6 +3,9 @@ import { MochaOptions, reporters, Runner } from 'mocha';
3
3
  import { ConfigLoader, TestStatusEnum } from 'qase-javascript-commons';
4
4
  type MochaState = 'failed' | 'passed' | 'pending';
5
5
  export declare class MochaQaseReporter extends reporters.Base {
6
+ private originalStdoutWrite;
7
+ private originalStderrWrite;
8
+ private testOutputs;
6
9
  /**
7
10
  * @type {Record<CypressState, TestStatusEnum>}
8
11
  */
@@ -23,6 +26,7 @@ export declare class MochaQaseReporter extends reporters.Base {
23
26
  private applyListeners;
24
27
  private onStartRun;
25
28
  private onEndRun;
29
+ private addMethodsToContext;
26
30
  private addMethods;
27
31
  private onStartTest;
28
32
  private onEndTest;
@@ -63,5 +67,21 @@ export declare class MochaQaseReporter extends reporters.Base {
63
67
  }) => void;
64
68
  comment: (message: string) => void;
65
69
  step: (title: string, func: () => void) => void;
70
+ /**
71
+ * @param {string} title
72
+ * @returns {string}
73
+ * @private
74
+ */
75
+ private removeQaseIdsFromTitle;
76
+ /**
77
+ * @type {RegExp}
78
+ */
79
+ static qaseIdRegExp: RegExp;
80
+ /**
81
+ * @param {string} title
82
+ * @returns {number[]}
83
+ * @private
84
+ */
85
+ private static getCaseId;
66
86
  }
67
87
  export {};
package/dist/reporter.js CHANGED
@@ -10,105 +10,41 @@ const qase_javascript_commons_1 = require("qase-javascript-commons");
10
10
  const deasync_promise_1 = __importDefault(require("deasync-promise"));
11
11
  const node_path_1 = require("node:path");
12
12
  const uuid_1 = require("uuid");
13
+ const interceptor_1 = require("./interceptor");
13
14
  const Events = mocha_1.Runner.constants;
14
15
  class currentTest {
15
- constructor() {
16
- this.steps = [];
17
- this.status = 'passed';
18
- this.attachments = [];
19
- }
16
+ steps = [];
17
+ status = 'passed';
18
+ attachments = [];
20
19
  }
21
20
  // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return,@typescript-eslint/restrict-template-expressions
22
21
  const resolveParallelModeSetupFile = () => (0, node_path_1.join)(__dirname, `parallel${(0, node_path_1.extname)(__filename)}`);
23
22
  class MochaQaseReporter extends mocha_1.reporters.Base {
23
+ originalStdoutWrite;
24
+ originalStderrWrite;
25
+ testOutputs;
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
+ };
34
+ /**
35
+ * @type {ReporterInterface}
36
+ * @private
37
+ */
38
+ reporter;
39
+ /**
40
+ * @type {Metadata}
41
+ * @private
42
+ */
43
+ metadata = new types_1.Metadata();
44
+ currentTest = new currentTest();
45
+ currentType = 'test';
24
46
  constructor(runner, options, configLoader = new qase_javascript_commons_1.ConfigLoader()) {
25
47
  super(runner, options);
26
- /**
27
- * @type {Metadata}
28
- * @private
29
- */
30
- this.metadata = new types_1.Metadata();
31
- this.currentTest = new currentTest();
32
- this.currentType = 'test';
33
- this.applyListeners = () => {
34
- this.runner.on(Events.EVENT_RUN_BEGIN, () => this.onStartRun());
35
- this.runner.on(Events.EVENT_RUN_END, () => this.onEndRun());
36
- this.runner.on(Events.EVENT_TEST_BEGIN, (test) => this.addMethods(test.ctx));
37
- this.runner.on(Events.EVENT_HOOK_BEGIN, (hook) => this.addMethods(hook.ctx));
38
- this.runner.on(Events.EVENT_TEST_BEGIN, () => this.onStartTest());
39
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
40
- this.runner.on(Events.EVENT_TEST_END, async (test) => await this.onEndTest(test));
41
- };
42
- this.qaseId = (id) => {
43
- this.metadata.addQaseId(id);
44
- };
45
- this.title = (title) => {
46
- this.metadata.title = title;
47
- };
48
- this.parameters = (values) => {
49
- const stringRecord = {};
50
- for (const [key, value] of Object.entries(values)) {
51
- stringRecord[String(key)] = String(value);
52
- }
53
- this.metadata.parameters = stringRecord;
54
- };
55
- this.groupParameters = (values) => {
56
- const stringRecord = {};
57
- for (const [key, value] of Object.entries(values)) {
58
- stringRecord[String(key)] = String(value);
59
- }
60
- this.metadata.groupParameters = stringRecord;
61
- };
62
- this.fields = (values) => {
63
- const stringRecord = {};
64
- for (const [key, value] of Object.entries(values)) {
65
- stringRecord[String(key)] = String(value);
66
- }
67
- this.metadata.fields = stringRecord;
68
- };
69
- this.suite = (name) => {
70
- this.metadata.suite = name;
71
- };
72
- this.ignore = () => {
73
- this.metadata.ignore = true;
74
- };
75
- this.attach = (attach) => {
76
- this.metadata.addAttachment(attach);
77
- };
78
- this.comment = (message) => {
79
- this.metadata.addComment(message);
80
- };
81
- this.step = (title, func) => {
82
- const previousType = this.currentType;
83
- this.currentType = 'step';
84
- const step = {
85
- step_type: qase_javascript_commons_1.StepType.TEXT,
86
- data: {
87
- action: title,
88
- expected_result: null,
89
- },
90
- execution: {
91
- start_time: Date.now(),
92
- status: qase_javascript_commons_1.StepStatusEnum.passed,
93
- end_time: null,
94
- duration: null,
95
- },
96
- id: '',
97
- parent_id: null,
98
- attachments: [],
99
- steps: [],
100
- };
101
- try {
102
- func();
103
- }
104
- catch (err) {
105
- step.execution.status = qase_javascript_commons_1.StepStatusEnum.failed;
106
- this.currentTest.status = 'failed';
107
- }
108
- step.execution.end_time = Date.now();
109
- this.currentTest.steps.push(step);
110
- this.currentType = previousType;
111
- };
112
48
  const config = configLoader.load();
113
49
  this.reporter = qase_javascript_commons_1.QaseReporter.getInstance({
114
50
  ...(0, qase_javascript_commons_1.composeOptions)(options, config),
@@ -123,14 +59,26 @@ class MochaQaseReporter extends mocha_1.reporters.Base {
123
59
  else {
124
60
  this.applyListeners();
125
61
  }
62
+ this.originalStdoutWrite = process.stdout.write.bind(process.stdout);
63
+ this.originalStderrWrite = process.stderr.write.bind(process.stderr);
64
+ this.testOutputs = new Map();
126
65
  }
66
+ applyListeners = () => {
67
+ this.runner.on(Events.EVENT_RUN_BEGIN, () => this.onStartRun());
68
+ this.runner.on(Events.EVENT_RUN_END, () => this.onEndRun());
69
+ this.runner.on(Events.EVENT_TEST_BEGIN, (test) => this.addMethods(test));
70
+ this.runner.on(Events.EVENT_HOOK_BEGIN, (hook) => this.addMethodsToContext(hook.ctx));
71
+ this.runner.on(Events.EVENT_TEST_BEGIN, () => this.onStartTest());
72
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
73
+ this.runner.on(Events.EVENT_TEST_END, (test) => this.onEndTest(test));
74
+ };
127
75
  onStartRun() {
128
76
  this.reporter.startTestRun();
129
77
  }
130
78
  onEndRun() {
131
79
  (0, deasync_promise_1.default)(this.reporter.publish());
132
80
  }
133
- addMethods(ctx) {
81
+ addMethodsToContext(ctx) {
134
82
  if (!ctx)
135
83
  return;
136
84
  ctx.qaseId = this.qaseId;
@@ -144,14 +92,50 @@ class MochaQaseReporter extends mocha_1.reporters.Base {
144
92
  ctx.comment = this.comment;
145
93
  ctx.step = this.step;
146
94
  }
95
+ addMethods(test) {
96
+ const stdoutInterceptor = new interceptor_1.StreamInterceptor((data) => {
97
+ const output = this.testOutputs.get(test.title) ?? { stdout: '', stderr: '' };
98
+ output.stdout += data;
99
+ this.testOutputs.set(test.title, output);
100
+ });
101
+ const stderrInterceptor = new interceptor_1.StreamInterceptor((data) => {
102
+ const output = this.testOutputs.get(test.title) ?? { stdout: '', stderr: '' };
103
+ output.stderr += data;
104
+ this.testOutputs.set(test.title, output);
105
+ });
106
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
107
+ // @ts-ignore
108
+ process.stdout.write = stdoutInterceptor.write.bind(stdoutInterceptor);
109
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
110
+ // @ts-ignore
111
+ process.stderr.write = stderrInterceptor.write.bind(stderrInterceptor);
112
+ this.testOutputs.set(test.title, { stdout: '', stderr: '' });
113
+ this.addMethodsToContext(test.ctx);
114
+ }
147
115
  onStartTest() {
148
116
  this.currentType = 'test';
149
117
  }
150
- async onEndTest(test) {
118
+ onEndTest(test) {
119
+ process.stdout.write = this.originalStdoutWrite;
120
+ process.stderr.write = this.originalStderrWrite;
121
+ if (this.reporter.isCaptureLogs()) {
122
+ const output = this.testOutputs.get(test.title);
123
+ if (output?.stdout) {
124
+ this.attach({ name: 'stdout.txt', content: output.stdout, contentType: 'text/plain' });
125
+ }
126
+ if (output?.stderr) {
127
+ this.attach({ name: 'stderr.txt', content: output.stderr, contentType: 'text/plain' });
128
+ }
129
+ }
151
130
  if (this.metadata.ignore) {
131
+ this.metadata.clear();
132
+ this.currentTest = new currentTest();
152
133
  return;
153
134
  }
154
135
  const ids = this.getQaseId();
136
+ if (ids.length === 0) {
137
+ ids.push(...MochaQaseReporter.getCaseId(test.title));
138
+ }
155
139
  const suites = this.getSuites(test);
156
140
  let relations = {};
157
141
  if (suites.length > 0) {
@@ -196,9 +180,9 @@ class MochaQaseReporter extends mocha_1.reporters.Base {
196
180
  thread: null,
197
181
  },
198
182
  testops_id: ids.length > 0 ? ids : null,
199
- title: this.metadata.title && this.metadata.title != '' ? this.metadata.title : test.title,
183
+ title: this.metadata.title && this.metadata.title != '' ? this.metadata.title : this.removeQaseIdsFromTitle(test.title),
200
184
  };
201
- await this.reporter.addTestResult(result);
185
+ (0, deasync_promise_1.default)(this.reporter.addTestResult(result));
202
186
  this.metadata.clear();
203
187
  this.currentTest = new currentTest();
204
188
  }
@@ -264,13 +248,100 @@ class MochaQaseReporter extends mocha_1.reporters.Base {
264
248
  }
265
249
  return suites;
266
250
  }
251
+ qaseId = (id) => {
252
+ this.metadata.addQaseId(id);
253
+ };
254
+ title = (title) => {
255
+ this.metadata.title = title;
256
+ };
257
+ parameters = (values) => {
258
+ const stringRecord = {};
259
+ for (const [key, value] of Object.entries(values)) {
260
+ stringRecord[String(key)] = String(value);
261
+ }
262
+ this.metadata.parameters = stringRecord;
263
+ };
264
+ groupParameters = (values) => {
265
+ const stringRecord = {};
266
+ for (const [key, value] of Object.entries(values)) {
267
+ stringRecord[String(key)] = String(value);
268
+ }
269
+ this.metadata.groupParameters = stringRecord;
270
+ };
271
+ fields = (values) => {
272
+ const stringRecord = {};
273
+ for (const [key, value] of Object.entries(values)) {
274
+ stringRecord[String(key)] = String(value);
275
+ }
276
+ this.metadata.fields = stringRecord;
277
+ };
278
+ suite = (name) => {
279
+ this.metadata.suite = name;
280
+ };
281
+ ignore = () => {
282
+ this.metadata.ignore = true;
283
+ };
284
+ attach = (attach) => {
285
+ this.metadata.addAttachment(attach);
286
+ };
287
+ comment = (message) => {
288
+ this.metadata.addComment(message);
289
+ };
290
+ step = (title, func) => {
291
+ const previousType = this.currentType;
292
+ this.currentType = 'step';
293
+ const step = {
294
+ step_type: qase_javascript_commons_1.StepType.TEXT,
295
+ data: {
296
+ action: title,
297
+ expected_result: null,
298
+ },
299
+ execution: {
300
+ start_time: Date.now(),
301
+ status: qase_javascript_commons_1.StepStatusEnum.passed,
302
+ end_time: null,
303
+ duration: null,
304
+ },
305
+ id: '',
306
+ parent_id: null,
307
+ attachments: [],
308
+ steps: [],
309
+ };
310
+ try {
311
+ func();
312
+ }
313
+ catch (err) {
314
+ step.execution.status = qase_javascript_commons_1.StepStatusEnum.failed;
315
+ this.currentTest.status = 'failed';
316
+ }
317
+ step.execution.end_time = Date.now();
318
+ this.currentTest.steps.push(step);
319
+ this.currentType = previousType;
320
+ };
321
+ /**
322
+ * @param {string} title
323
+ * @returns {string}
324
+ * @private
325
+ */
326
+ removeQaseIdsFromTitle(title) {
327
+ const matches = title.match(MochaQaseReporter.qaseIdRegExp);
328
+ if (matches) {
329
+ return title.replace(matches[0], '').trimEnd();
330
+ }
331
+ return title;
332
+ }
333
+ /**
334
+ * @type {RegExp}
335
+ */
336
+ static qaseIdRegExp = /\(Qase ID: ([\d,]+)\)/;
337
+ /**
338
+ * @param {string} title
339
+ * @returns {number[]}
340
+ * @private
341
+ */
342
+ static getCaseId(title) {
343
+ const [, ids] = title.match(MochaQaseReporter.qaseIdRegExp) ?? [];
344
+ return ids ? ids.split(',').map((id) => Number(id)) : [];
345
+ }
267
346
  }
268
347
  exports.MochaQaseReporter = MochaQaseReporter;
269
- /**
270
- * @type {Record<CypressState, TestStatusEnum>}
271
- */
272
- MochaQaseReporter.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
- };
package/dist/types.d.ts CHANGED
@@ -13,6 +13,10 @@ export interface Hook extends Mocha.Hook {
13
13
  ctx?: Context;
14
14
  }
15
15
  export interface Methods {
16
+ /**
17
+ * Set IDs for the test case
18
+ * Use `qase()` instead. This method is deprecated and kept for reverse compatibility.
19
+ */
16
20
  qaseId(id: number | number[]): void;
17
21
  title(title: string): void;
18
22
  parameters(values: Record<string, string>): void;
package/dist/types.js CHANGED
@@ -7,6 +7,15 @@ exports.Metadata = void 0;
7
7
  const qase_javascript_commons_1 = require("qase-javascript-commons");
8
8
  const path_1 = __importDefault(require("path"));
9
9
  class Metadata {
10
+ ids;
11
+ title;
12
+ fields;
13
+ parameters;
14
+ groupParameters;
15
+ ignore;
16
+ suite;
17
+ comment;
18
+ attachments;
10
19
  constructor() {
11
20
  this.clear();
12
21
  }
@@ -31,6 +40,7 @@ class Metadata {
31
40
  id: '',
32
41
  });
33
42
  }
43
+ return;
34
44
  }
35
45
  this.attachments?.push({
36
46
  file_name: attach.name ?? '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mocha-qase-reporter",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0",
4
4
  "description": "Mocha Cypress Reporter",
5
5
  "homepage": "https://github.com/qase-tms/qase-javascript",
6
6
  "sideEffects": false,
@@ -9,7 +9,8 @@
9
9
  "exports": {
10
10
  ".": "./dist/index.js",
11
11
  "./reporter": "./dist/reporter.js",
12
- "./package.json": "./package.json"
12
+ "./package.json": "./package.json",
13
+ "./mocha": "./dist/mocha.js"
13
14
  },
14
15
  "typesVersions": {
15
16
  "*": {