qase-javascript-commons 2.0.0-beta.8 → 2.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
@@ -1,11 +1,25 @@
1
1
  # Qase JavaScript Commons
2
2
 
3
- This package contains common classes and functions for working with Qase TMS API.
3
+ This module is an SDK for developing test reporters for Qase TMS.
4
+ It's using `qaseio` as an API client, and all Qase reporters are, in turn,
5
+ using this package.
6
+ You should use it if you're developing your own test reporter for a special-purpose framework.
7
+
8
+ To report results from tests using a popular framework or test runner,
9
+ don't install this module directly and
10
+ use the corresponding reporter module instead:
11
+
12
+ * [CucumberJS](https://github.com/qase-tms/qase-javascript/tree/main/qase-cucumberjs#readme)
13
+ * [Cypress](https://github.com/qase-tms/qase-javascript/tree/main/qase-cypress#readme)
14
+ * [Jest](https://github.com/qase-tms/qase-javascript/tree/main/qase-jest#readme)
15
+ * [Newman](https://github.com/qase-tms/qase-javascript/tree/main/qase-newman#readme)
16
+ * [Playwright](https://github.com/qase-tms/qase-javascript/tree/main/qase-playwright#readme)
17
+ * [TestCafe](https://github.com/qase-tms/qase-javascript/tree/main/qase-testcafe#readme)
4
18
 
5
19
  ## Installation
6
20
 
7
21
  ```bash
8
- npm install qase-javascript-commons@beta
22
+ npm install qase-javascript-commons
9
23
  ```
10
24
 
11
25
  ## Configuration
package/changelog.md CHANGED
@@ -1,3 +1,101 @@
1
+ # qase-javascript-commons@2.0.0
2
+
3
+ ## What's new
4
+
5
+ This is the first release version of the Qase JavaScript SDK.
6
+ It is numbered `2.0.0` (and not `1.0.0`) to match the release series of
7
+ test reporters for Playwright, Cypress, Jest and other frameworks.
8
+
9
+ ### Annotating test with field data
10
+
11
+ Tests can now be annotated with data for system and custom fields in Qase.
12
+ This feature is already implemented in the Playwright reporter:
13
+
14
+ ```js
15
+ test('Test with annotated fields', () => {
16
+ qase.id(1);
17
+ qase.fields({ 'severity': 'high', 'priority': 'medium' })
18
+ // ...
19
+ });
20
+ ```
21
+
22
+ ### Parametrized tests
23
+
24
+ Qase JavaScript SDK enables annotating tests with parameters, and passing parameter values to Qase.
25
+ Parameterized tests can report to a single test case, but each parameter variation is registered as
26
+ a standalone result, with its own run history.
27
+
28
+ ### Attachments from files and variables
29
+
30
+ Reporters can now upload attachments of various data types to Qase,
31
+ both from files and from variables.
32
+ It enables flexible and meticulous logging, such as collecting full HTTP request data,
33
+ including URL, headers, and payload.
34
+
35
+ ### Uploading results in flexible batches, asynchronously
36
+
37
+ Test reporters can now upload results in batches, while tests are still running.
38
+ It helps bring test results faster and enables acting on them long before the test run is complete.
39
+
40
+ ### Uniform configuration
41
+
42
+ Qase JavaScript SDK brings configuration with config files and environment variables
43
+ to a common standard, used with Qase reporters in all languages and frameworks.
44
+
45
+ For details, see the Configuration section in the README.
46
+
47
+ ### Latest API
48
+
49
+ Qase JavaScript SDK is using the latest Qase API client,
50
+ employing the full power of the stable v1 API version,
51
+ and enabling the use of experimental v2 API, tailored for uploading
52
+ huge amounts of test results.
53
+
54
+ # qase-javascript-commons@2.0.0-beta.12
55
+
56
+ ## What's new
57
+
58
+ Fixed an issue when the result has empty step action. Now the reporter will mark such steps as "Unnamed step" and will
59
+ log a warning message.
60
+
61
+ # qase-javascript-commons@2.0.0-beta.11
62
+
63
+ ## What's new
64
+
65
+ * The `useV2` option in the reporter's configuration will now enable using the experimental v2 API.
66
+ Before this fix, v1 API was used despite the configuration.
67
+
68
+ * Attachments from test steps will now be uploaded to Qase.
69
+ Before this fix, the reporter uploaded only the attachments made outside of any step scope.
70
+
71
+ # qase-javascript-commons@2.0.0-beta.10
72
+
73
+ ## What's new
74
+
75
+ Fixed an issue when the results published before the test run creation.
76
+
77
+ # qase-javascript-commons@2.0.0-beta.9
78
+
79
+ ## What's new
80
+
81
+ Improved debug logging for better testing and reporting errors.
82
+
83
+ - Separate `logger` class for use in reporters, supporting logging to console and files.
84
+ - Extra debug logs in both reporter modes: TestOps and Local.
85
+
86
+ Fixed an issue with duplicate test runs created when the testing framework
87
+ (such as Cypress) uses more than one instance of the Qase reporter.
88
+ Now reporter handles Qase test runs in the following way:
89
+
90
+ 1. The first instance of the reporter creates a Qase test run and stores the run ID
91
+ in the ENV variable `QASE_TESTOPS_RUN_ID`.
92
+ 2. Other instances of the reporter read this variable and report test results
93
+ to the existing test run.
94
+
95
+ Nothing has changed in cases when there is a single instance of a reporter or
96
+ when it is using a test run, created with other tools, such as with an API request
97
+ or manually in the Qase app.
98
+
1
99
  # qase-javascript-commons@2.0.0-beta.8
2
100
 
3
101
  ## What's new
@@ -1,6 +1,6 @@
1
1
  export { type TestResultType, Relation, Suite, SuiteData } from './test-result';
2
2
  export { TestExecution, TestStatusEnum } from './test-execution';
3
- export { type TestStepType } from './test-step';
3
+ export { type TestStepType, StepType } from './test-step';
4
4
  export { StepStatusEnum } from './step-execution';
5
5
  export { Attachment } from './attachment';
6
6
  export { Report } from './report';
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StepStatusEnum = exports.TestStatusEnum = void 0;
3
+ exports.StepStatusEnum = exports.StepType = exports.TestStatusEnum = void 0;
4
4
  var test_execution_1 = require("./test-execution");
5
5
  Object.defineProperty(exports, "TestStatusEnum", { enumerable: true, get: function () { return test_execution_1.TestStatusEnum; } });
6
+ var test_step_1 = require("./test-step");
7
+ Object.defineProperty(exports, "StepType", { enumerable: true, get: function () { return test_step_1.StepType; } });
6
8
  var step_execution_1 = require("./step-execution");
7
9
  Object.defineProperty(exports, "StepStatusEnum", { enumerable: true, get: function () { return step_execution_1.StepStatusEnum; } });
@@ -1,4 +1,9 @@
1
- export interface StepData {
1
+ export interface StepTextData {
2
2
  action: string;
3
3
  expected_result: string | null;
4
4
  }
5
+ export interface StepGherkinData {
6
+ keyword: string;
7
+ name: string;
8
+ line: number;
9
+ }
@@ -1,10 +1,14 @@
1
- import { StepData } from './step-data';
1
+ import { StepGherkinData, StepTextData } from './step-data';
2
2
  import { StepExecution } from './step-execution';
3
3
  import { Attachment } from './attachment';
4
+ export declare enum StepType {
5
+ TEXT = "text",
6
+ GHERKIN = "gherkin"
7
+ }
4
8
  export type TestStepType = {
5
9
  id: string;
6
- step_type: string;
7
- data: StepData;
10
+ step_type: StepType;
11
+ data: StepTextData | StepGherkinData;
8
12
  parent_id: string | null;
9
13
  execution: StepExecution;
10
14
  attachments: Attachment[];
@@ -1,2 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StepType = void 0;
4
+ var StepType;
5
+ (function (StepType) {
6
+ StepType["TEXT"] = "text";
7
+ StepType["GHERKIN"] = "gherkin";
8
+ })(StepType || (exports.StepType = StepType = {}));
package/dist/qase.d.ts CHANGED
@@ -1,11 +1,17 @@
1
- import { AbstractReporter, LoggerInterface } from './reporters';
2
1
  import { OptionsType } from './options';
3
2
  import { TestResultType } from './models';
3
+ export interface ReporterInterface {
4
+ addTestResult(result: TestResultType): Promise<void>;
5
+ publish(): Promise<void>;
6
+ startTestRun(): void;
7
+ isCaptureLogs(): boolean;
8
+ }
4
9
  /**
5
10
  * @class QaseReporter
6
11
  * @implements AbstractReporter
7
12
  */
8
- export declare class QaseReporter extends AbstractReporter {
13
+ export declare class QaseReporter implements ReporterInterface {
14
+ private static instance;
9
15
  /**
10
16
  * @param {string} frameworkPackage
11
17
  * @param {string} frameworkName
@@ -15,15 +21,20 @@ export declare class QaseReporter extends AbstractReporter {
15
21
  */
16
22
  private static createHeaders;
17
23
  /**
18
- * @type {ReporterInterface}
24
+ * @type {InternalReporterInterface}
19
25
  * @private
20
26
  */
21
27
  private readonly upstreamReporter?;
22
28
  /**
23
- * @type {ReporterInterface}
29
+ * @type {InternalReporterInterface}
24
30
  * @private
25
31
  */
26
32
  private readonly fallbackReporter?;
33
+ /**
34
+ * @type {boolean | undefined}
35
+ * @private
36
+ */
37
+ private readonly captureLogs;
27
38
  /**
28
39
  * @type {boolean}
29
40
  * @private
@@ -34,15 +45,21 @@ export declare class QaseReporter extends AbstractReporter {
34
45
  * @private
35
46
  */
36
47
  private useFallback;
48
+ private readonly logger;
49
+ private startTestRunOperation?;
37
50
  /**
38
51
  * @param {OptionsType} options
39
- * @param {LoggerInterface} logger
40
52
  */
41
- constructor(options: OptionsType, logger?: LoggerInterface);
53
+ private constructor();
42
54
  /**
43
- * @returns {Promise<void>}
55
+ * @returns {void}
56
+ */
57
+ startTestRun(): void;
58
+ /**
59
+ * @param {OptionsType} options
60
+ * @returns {QaseReporter}
44
61
  */
45
- startTestRun(): Promise<void>;
62
+ static getInstance(options: OptionsType): QaseReporter;
46
63
  /**
47
64
  * @param {TestResultType} result
48
65
  */
@@ -52,6 +69,10 @@ export declare class QaseReporter extends AbstractReporter {
52
69
  * @private
53
70
  */
54
71
  private addTestResultToFallback;
72
+ /**
73
+ * @returns {boolean}
74
+ */
75
+ isCaptureLogs(): boolean;
55
76
  /**
56
77
  * @returns {Promise<void>}
57
78
  */
@@ -64,8 +85,7 @@ export declare class QaseReporter extends AbstractReporter {
64
85
  * @todo implement mode registry
65
86
  * @param {ModeEnum} mode
66
87
  * @param {OptionsType} options
67
- * @param {LoggerInterface} logger
68
- * @returns {ReporterInterface}
88
+ * @returns {InternalReporterInterface}
69
89
  * @private
70
90
  */
71
91
  private createReporter;
package/dist/qase.js CHANGED
@@ -16,6 +16,7 @@ const writer_1 = require("./writer");
16
16
  const get_package_version_1 = require("./utils/get-package-version");
17
17
  const custom_boundary_1 = require("./utils/custom-boundary");
18
18
  const disabled_exception_1 = require("./utils/disabled-exception");
19
+ const logger_1 = require("./utils/logger");
19
20
  /**
20
21
  * @type {Record<TestStatusEnum, (test: TestResultType) => string>}
21
22
  */
@@ -31,7 +32,7 @@ const resultLogMap = {
31
32
  * @class QaseReporter
32
33
  * @implements AbstractReporter
33
34
  */
34
- class QaseReporter extends reporters_1.AbstractReporter {
35
+ class QaseReporter {
35
36
  /**
36
37
  * @param {string} frameworkPackage
37
38
  * @param {string} frameworkName
@@ -66,12 +67,8 @@ class QaseReporter extends reporters_1.AbstractReporter {
66
67
  }
67
68
  /**
68
69
  * @param {OptionsType} options
69
- * @param {LoggerInterface} logger
70
70
  */
71
- constructor(options, logger) {
72
- const env = (0, env_1.envToConfig)((0, env_schema_1.default)({ schema: env_1.envValidationSchema }));
73
- const composedOptions = (0, options_1.composeOptions)(options, env);
74
- super({ debug: composedOptions.debug, captureLogs: composedOptions.captureLogs }, logger);
71
+ constructor(options) {
75
72
  /**
76
73
  * @type {boolean}
77
74
  * @private
@@ -82,17 +79,22 @@ class QaseReporter extends reporters_1.AbstractReporter {
82
79
  * @private
83
80
  */
84
81
  this.useFallback = false;
82
+ const env = (0, env_1.envToConfig)((0, env_schema_1.default)({ schema: env_1.envValidationSchema }));
83
+ const composedOptions = (0, options_1.composeOptions)(options, env);
84
+ this.logger = new logger_1.Logger({ debug: composedOptions.debug });
85
+ this.logger.logDebug(`Config: ${JSON.stringify(composedOptions)}`);
86
+ this.captureLogs = composedOptions.captureLogs;
85
87
  try {
86
88
  this.upstreamReporter = this.createReporter(
87
89
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
88
- composedOptions.mode || options_1.ModeEnum.off, composedOptions, logger);
90
+ composedOptions.mode || options_1.ModeEnum.off, composedOptions);
89
91
  }
90
92
  catch (error) {
91
93
  if (error instanceof disabled_exception_1.DisabledException) {
92
94
  this.disabled = true;
93
95
  }
94
96
  else {
95
- this.logError('Unable to create upstream reporter:', error);
97
+ this.logger.logError('Unable to create upstream reporter:', error);
96
98
  if (composedOptions.fallback != undefined) {
97
99
  this.disabled = true;
98
100
  return;
@@ -103,7 +105,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
103
105
  try {
104
106
  this.fallbackReporter = this.createReporter(
105
107
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
106
- composedOptions.fallback || options_1.ModeEnum.off, composedOptions, logger);
108
+ composedOptions.fallback || options_1.ModeEnum.off, composedOptions);
107
109
  }
108
110
  catch (error) {
109
111
  if (error instanceof disabled_exception_1.DisabledException) {
@@ -112,7 +114,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
112
114
  }
113
115
  }
114
116
  else {
115
- this.logError('Unable to create fallback reporter:', error);
117
+ this.logger.logError('Unable to create fallback reporter:', error);
116
118
  if (this.useFallback && this.upstreamReporter === undefined) {
117
119
  this.disabled = true;
118
120
  }
@@ -120,29 +122,40 @@ class QaseReporter extends reporters_1.AbstractReporter {
120
122
  }
121
123
  }
122
124
  /**
123
- * @returns {Promise<void>}
125
+ * @returns {void}
124
126
  */
125
- async startTestRun() {
127
+ startTestRun() {
126
128
  if (!this.disabled) {
129
+ this.logger.logDebug('Starting test run');
127
130
  try {
128
- await this.upstreamReporter?.startTestRun();
131
+ this.startTestRunOperation = this.upstreamReporter?.startTestRun();
129
132
  }
130
133
  catch (error) {
131
- this.logError('Unable to start test run in the upstream reporter: ', error);
134
+ this.logger.logError('Unable to start test run in the upstream reporter: ', error);
132
135
  if (this.fallbackReporter == undefined) {
133
136
  this.disabled = true;
134
137
  return;
135
138
  }
136
139
  try {
137
- await this.fallbackReporter?.startTestRun();
140
+ this.startTestRunOperation = this.fallbackReporter?.startTestRun();
138
141
  }
139
142
  catch (error) {
140
- this.logError('Unable to start test run in the fallback reporter: ', error);
143
+ this.logger.logError('Unable to start test run in the fallback reporter: ', error);
141
144
  this.disabled = true;
142
145
  }
143
146
  }
144
147
  }
145
148
  }
149
+ /**
150
+ * @param {OptionsType} options
151
+ * @returns {QaseReporter}
152
+ */
153
+ static getInstance(options) {
154
+ if (!QaseReporter.instance) {
155
+ QaseReporter.instance = new QaseReporter(options);
156
+ }
157
+ return QaseReporter.instance;
158
+ }
146
159
  /**
147
160
  * @param {TestResultType} result
148
161
  */
@@ -157,7 +170,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
157
170
  await this.upstreamReporter?.addTestResult(result);
158
171
  }
159
172
  catch (error) {
160
- this.logError('Unable to add the result to the upstream reporter:', error);
173
+ this.logger.logError('Unable to add the result to the upstream reporter:', error);
161
174
  if (this.fallbackReporter == undefined) {
162
175
  this.disabled = true;
163
176
  return;
@@ -179,15 +192,23 @@ class QaseReporter extends reporters_1.AbstractReporter {
179
192
  await this.fallbackReporter?.addTestResult(result);
180
193
  }
181
194
  catch (error) {
182
- this.logError('Unable to add the result to the fallback reporter:', error);
195
+ this.logger.logError('Unable to add the result to the fallback reporter:', error);
183
196
  this.disabled = true;
184
197
  }
185
198
  }
199
+ /**
200
+ * @returns {boolean}
201
+ */
202
+ isCaptureLogs() {
203
+ return this.captureLogs ?? false;
204
+ }
186
205
  /**
187
206
  * @returns {Promise<void>}
188
207
  */
189
208
  async publish() {
190
209
  if (!this.disabled) {
210
+ await this.startTestRunOperation;
211
+ this.logger.logDebug('Publishing test run results');
191
212
  if (this.useFallback) {
192
213
  await this.publishFallback();
193
214
  }
@@ -195,7 +216,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
195
216
  await this.upstreamReporter?.publish();
196
217
  }
197
218
  catch (error) {
198
- this.logError('Unable to publish the run results to the upstream reporter:', error);
219
+ this.logger.logError('Unable to publish the run results to the upstream reporter:', error);
199
220
  if (this.fallbackReporter == undefined) {
200
221
  this.disabled = true;
201
222
  return;
@@ -216,7 +237,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
216
237
  await this.fallbackReporter?.publish();
217
238
  }
218
239
  catch (error) {
219
- this.logError('Unable to publish the run results to the fallback reporter:', error);
240
+ this.logger.logError('Unable to publish the run results to the fallback reporter:', error);
220
241
  this.disabled = true;
221
242
  }
222
243
  }
@@ -224,15 +245,14 @@ class QaseReporter extends reporters_1.AbstractReporter {
224
245
  * @todo implement mode registry
225
246
  * @param {ModeEnum} mode
226
247
  * @param {OptionsType} options
227
- * @param {LoggerInterface} logger
228
- * @returns {ReporterInterface}
248
+ * @returns {InternalReporterInterface}
229
249
  * @private
230
250
  */
231
- createReporter(mode, options, logger) {
232
- const { frameworkPackage, frameworkName, reporterName, environment, report = {}, testops = {}, ...commonOptions } = options;
251
+ createReporter(mode, options) {
252
+ const { frameworkPackage, frameworkName, reporterName, environment, report = {}, testops = {}, } = options;
233
253
  switch (mode) {
234
254
  case options_1.ModeEnum.testops: {
235
- const { api: { token, headers, ...api } = {}, project, run: { title, description, ...run } = {}, plan = {}, batch = {}, uploadAttachments, } = testops;
255
+ const { api: { token, headers, ...api } = {}, project, run: { title, description, ...run } = {}, plan = {}, batch = {}, useV2, uploadAttachments, } = testops;
236
256
  if (!token) {
237
257
  throw new Error(`Either "testops.api.token" parameter or "${env_1.EnvApiEnum.token}" environment variable is required in "testops" mode`);
238
258
  }
@@ -247,7 +267,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
247
267
  },
248
268
  ...api,
249
269
  }, custom_boundary_1.CustomBoundaryFormData);
250
- return new reporters_1.TestOpsReporter({
270
+ return new reporters_1.TestOpsReporter(this.logger, {
251
271
  project,
252
272
  uploadAttachments,
253
273
  run: {
@@ -257,17 +277,13 @@ class QaseReporter extends reporters_1.AbstractReporter {
257
277
  },
258
278
  plan,
259
279
  batch,
260
- debug: commonOptions.debug,
261
- captureLogs: commonOptions.captureLogs,
262
- }, apiClient, logger, typeof environment === 'number' ? environment : undefined);
280
+ useV2,
281
+ }, apiClient, typeof environment === 'number' ? environment : undefined);
263
282
  }
264
283
  case options_1.ModeEnum.report: {
265
284
  const localOptions = report.connections?.[writer_1.DriverEnum.local];
266
285
  const writer = new writer_1.FsWriter(localOptions);
267
- return new reporters_1.ReportReporter({
268
- debug: commonOptions.debug,
269
- captureLogs: commonOptions.captureLogs,
270
- }, writer, logger, typeof environment === 'number' ? environment.toString() : environment, testops.run?.id);
286
+ return new reporters_1.ReportReporter(this.logger, writer, typeof environment === 'number' ? environment.toString() : environment, testops.run?.id);
271
287
  }
272
288
  case options_1.ModeEnum.off:
273
289
  throw new disabled_exception_1.DisabledException();
@@ -280,7 +296,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
280
296
  * @private
281
297
  */
282
298
  logTestItem(test) {
283
- this.log(resultLogMap[test.execution.status](test));
299
+ this.logger.log(resultLogMap[test.execution.status](test));
284
300
  }
285
301
  }
286
302
  exports.QaseReporter = QaseReporter;
@@ -1,38 +1,23 @@
1
1
  import { TestResultType } from '../models';
2
- export interface LoggerInterface {
3
- log(message: string): void;
4
- group(): void;
5
- groupEnd(): void;
6
- }
7
- export interface ReporterOptionsType {
8
- debug?: boolean | undefined;
9
- captureLogs?: boolean | undefined;
10
- }
11
- export interface ReporterInterface {
2
+ import { LoggerInterface } from '../utils/logger';
3
+ export interface InternalReporterInterface {
12
4
  addTestResult(result: TestResultType): Promise<void>;
13
5
  publish(): Promise<void>;
14
6
  startTestRun(): Promise<void>;
15
7
  getTestResults(): TestResultType[];
16
8
  setTestResults(results: TestResultType[]): void;
17
- isCaptureLogs(): boolean;
18
9
  }
19
10
  /**
20
11
  * @abstract
21
12
  * @class AbstractReporter
22
- * @implements ReporterInterface
13
+ * @implements InternalReporterInterface
23
14
  */
24
- export declare abstract class AbstractReporter implements ReporterInterface {
25
- private logger;
26
- /**
27
- * @type {boolean | undefined}
28
- * @private
29
- */
30
- private readonly debug;
15
+ export declare abstract class AbstractReporter implements InternalReporterInterface {
31
16
  /**
32
- * @type {boolean | undefined}
17
+ * @type {LoggerInterface}
33
18
  * @private
34
19
  */
35
- private readonly captureLogs;
20
+ protected readonly logger: LoggerInterface;
36
21
  /**
37
22
  * @type {TestResultType[]}
38
23
  * @protected
@@ -47,19 +32,14 @@ export declare abstract class AbstractReporter implements ReporterInterface {
47
32
  */
48
33
  abstract startTestRun(): Promise<void>;
49
34
  /**
50
- * @param {ReporterOptionsType} options
51
- * @param {LoggerInterface} logger
52
35
  * @protected
36
+ * @param {LoggerInterface} logger
53
37
  */
54
- protected constructor(options: ReporterOptionsType | undefined, logger?: LoggerInterface);
38
+ protected constructor(logger: LoggerInterface);
55
39
  /**
56
40
  * @returns {TestResultType[]}
57
41
  */
58
42
  getTestResults(): TestResultType[];
59
- /**
60
- * @returns {boolean}
61
- */
62
- isCaptureLogs(): boolean;
63
43
  /**
64
44
  * @param {TestResultType} result
65
45
  */
@@ -68,32 +48,4 @@ export declare abstract class AbstractReporter implements ReporterInterface {
68
48
  * @param {TestResultType[]} results
69
49
  */
70
50
  setTestResults(results: TestResultType[]): void;
71
- /**
72
- * @param {string} message
73
- * @protected
74
- */
75
- protected log(message: string): void;
76
- /**
77
- * @param {string} message
78
- * @param error
79
- * @protected
80
- */
81
- protected logError(message: string, error?: unknown): void;
82
- /**
83
- * @param {string} message
84
- * @param error
85
- * @private
86
- */
87
- private doLogError;
88
- /**
89
- * @param {AxiosError} error
90
- * @private
91
- */
92
- private logApiError;
93
- /**
94
- * @param errorFields
95
- * @returns {string | undefined}
96
- * @private
97
- */
98
- private formatErrorFields;
99
51
  }
@@ -1,34 +1,24 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.AbstractReporter = void 0;
7
- const lodash_get_1 = __importDefault(require("lodash.get"));
8
- const qase_error_1 = require("../utils/qase-error");
9
- const is_axios_error_1 = require("../utils/is-axios-error");
10
4
  const uuid_1 = require("uuid");
11
5
  /**
12
6
  * @abstract
13
7
  * @class AbstractReporter
14
- * @implements ReporterInterface
8
+ * @implements InternalReporterInterface
15
9
  */
16
10
  class AbstractReporter {
17
11
  /**
18
- * @param {ReporterOptionsType} options
19
- * @param {LoggerInterface} logger
20
12
  * @protected
13
+ * @param {LoggerInterface} logger
21
14
  */
22
- constructor(options, logger = console) {
23
- this.logger = logger;
15
+ constructor(logger) {
24
16
  /**
25
17
  * @type {TestResultType[]}
26
18
  * @protected
27
19
  */
28
20
  this.results = [];
29
- const { debug, captureLogs } = options ?? {};
30
- this.debug = debug;
31
- this.captureLogs = captureLogs;
21
+ this.logger = logger;
32
22
  }
33
23
  /**
34
24
  * @returns {TestResultType[]}
@@ -36,17 +26,12 @@ class AbstractReporter {
36
26
  getTestResults() {
37
27
  return this.results;
38
28
  }
39
- /**
40
- * @returns {boolean}
41
- */
42
- isCaptureLogs() {
43
- return this.captureLogs ?? false;
44
- }
45
29
  /**
46
30
  * @param {TestResultType} result
47
31
  */
48
32
  // eslint-disable-next-line @typescript-eslint/require-await
49
33
  async addTestResult(result) {
34
+ this.logger.logDebug(`Adding test result: ${JSON.stringify(result)}`);
50
35
  if (result.testops_id === null || !Array.isArray(result.testops_id)) {
51
36
  this.results.push(result);
52
37
  return;
@@ -70,79 +55,5 @@ class AbstractReporter {
70
55
  setTestResults(results) {
71
56
  this.results = results;
72
57
  }
73
- /**
74
- * @param {string} message
75
- * @protected
76
- */
77
- log(message) {
78
- if (this.debug) {
79
- this.logger.log(`qase: ${message}`);
80
- }
81
- }
82
- /**
83
- * @param {string} message
84
- * @param error
85
- * @protected
86
- */
87
- logError(message, error) {
88
- this.doLogError(`qase: ${message}`, error);
89
- }
90
- /**
91
- * @param {string} message
92
- * @param error
93
- * @private
94
- */
95
- doLogError(message, error) {
96
- this.logger.log(message);
97
- this.logger.group();
98
- if (error instanceof Error) {
99
- if ((0, is_axios_error_1.isAxiosError)(error)) {
100
- this.logApiError(error);
101
- }
102
- else if (error instanceof qase_error_1.QaseError && error.cause) {
103
- this.doLogError('Caused by:', error.cause);
104
- }
105
- this.logger.log(`${error.stack || `${error.name}: ${error.message}`}`);
106
- }
107
- else {
108
- this.logger.log(String(error));
109
- }
110
- this.logger.groupEnd();
111
- }
112
- /**
113
- * @param {AxiosError} error
114
- * @private
115
- */
116
- logApiError(error) {
117
- const errorMessage = (0, lodash_get_1.default)(error, 'response.data.errorMessage')
118
- ?? (0, lodash_get_1.default)(error, 'response.data.error')
119
- ?? (0, lodash_get_1.default)(error, 'response.statusText')
120
- ?? 'Unknown error';
121
- const errorFields = this.formatErrorFields((0, lodash_get_1.default)(error, 'response.data.errorFields'));
122
- this.logger.log(`Message: ${String(errorMessage)}`);
123
- if (errorFields) {
124
- this.logger.group();
125
- this.logger.log(errorFields);
126
- this.logger.groupEnd();
127
- }
128
- }
129
- /**
130
- * @param errorFields
131
- * @returns {string | undefined}
132
- * @private
133
- */
134
- formatErrorFields(errorFields) {
135
- if (Array.isArray(errorFields)) {
136
- return errorFields.reduce((acc, item) => {
137
- const field = (0, lodash_get_1.default)(item, 'field');
138
- const error = (0, lodash_get_1.default)(item, 'error');
139
- if (field && error) {
140
- return acc + `${String(field)}: ${String(error)}\n`;
141
- }
142
- return acc;
143
- }, '');
144
- }
145
- return undefined;
146
- }
147
58
  }
148
59
  exports.AbstractReporter = AbstractReporter;
@@ -1,3 +1,3 @@
1
- export { AbstractReporter, type ReporterInterface, type ReporterOptionsType, type LoggerInterface, } from './abstract-reporter';
1
+ export { AbstractReporter, type InternalReporterInterface, } from './abstract-reporter';
2
2
  export { ReportReporter } from './report-reporter';
3
3
  export { TestOpsReporter, type TestOpsOptionsType } from './testops-reporter';
@@ -1,5 +1,6 @@
1
- import { AbstractReporter, LoggerInterface, ReporterOptionsType } from './abstract-reporter';
1
+ import { AbstractReporter } from './abstract-reporter';
2
2
  import { WriterInterface } from '../writer';
3
+ import { LoggerInterface } from '../utils/logger';
3
4
  /**
4
5
  * @class ReportReporter
5
6
  * @extends AbstractReporter
@@ -10,13 +11,12 @@ export declare class ReportReporter extends AbstractReporter {
10
11
  private readonly runId;
11
12
  private startTime;
12
13
  /**
13
- * @param {ReporterOptionsType} options
14
- * @param {WriterInterface} writer
15
14
  * @param {LoggerInterface} logger
15
+ * @param {WriterInterface} writer
16
16
  * @param {string | undefined} environment
17
17
  * @param {number | undefined} runId
18
18
  */
19
- constructor(options: ReporterOptionsType | undefined, writer: WriterInterface, logger?: LoggerInterface, environment?: string, runId?: number);
19
+ constructor(logger: LoggerInterface, writer: WriterInterface, environment?: string, runId?: number);
20
20
  /**
21
21
  * @returns {Promise<void>}
22
22
  */
@@ -35,14 +35,13 @@ const process = __importStar(require("process"));
35
35
  */
36
36
  class ReportReporter extends abstract_reporter_1.AbstractReporter {
37
37
  /**
38
- * @param {ReporterOptionsType} options
39
- * @param {WriterInterface} writer
40
38
  * @param {LoggerInterface} logger
39
+ * @param {WriterInterface} writer
41
40
  * @param {string | undefined} environment
42
41
  * @param {number | undefined} runId
43
42
  */
44
- constructor(options, writer, logger, environment, runId) {
45
- super(options, logger);
43
+ constructor(logger, writer, environment, runId) {
44
+ super(logger);
46
45
  this.writer = writer;
47
46
  this.startTime = Date.now();
48
47
  this.environment = environment;
@@ -60,6 +59,7 @@ class ReportReporter extends abstract_reporter_1.AbstractReporter {
60
59
  *
61
60
  */
62
61
  async publish() {
62
+ this.writer.clearPreviousResults();
63
63
  const report = {
64
64
  title: 'Test report',
65
65
  execution: {
@@ -120,7 +120,7 @@ class ReportReporter extends abstract_reporter_1.AbstractReporter {
120
120
  await this.writer.writeTestResult(result);
121
121
  }
122
122
  const path = await this.writer.writeReport(report);
123
- this.log(`Report saved to ${path}`);
123
+ this.logger.log(`Report saved to ${path}`);
124
124
  }
125
125
  /**
126
126
  * @param {TestStepType[]} steps
@@ -1,6 +1,7 @@
1
1
  import { QaseApiInterface, ResultStepStatus, TestStepResultCreateStatusEnum } from 'qaseio';
2
- import { AbstractReporter, LoggerInterface, ReporterOptionsType } from './abstract-reporter';
2
+ import { AbstractReporter } from './abstract-reporter';
3
3
  import { StepStatusEnum, TestResultType, TestStatusEnum } from '../models';
4
+ import { LoggerInterface } from '../utils/logger';
4
5
  export type TestOpsRunType = {
5
6
  id?: number | undefined;
6
7
  title: string;
@@ -91,12 +92,12 @@ export declare class TestOpsReporter extends AbstractReporter {
91
92
  */
92
93
  private isTestRunReady;
93
94
  /**
95
+ * @param {LoggerInterface} logger
94
96
  * @param {ReporterOptionsType & TestOpsOptionsType} options
95
97
  * @param {QaseApiInterface} api
96
- * @param {LoggerInterface} logger
97
98
  * @param {number} environment
98
99
  */
99
- constructor(options: ReporterOptionsType & TestOpsOptionsType, api: QaseApiInterface, logger?: LoggerInterface, environment?: number);
100
+ constructor(logger: LoggerInterface, options: TestOpsOptionsType, api: QaseApiInterface, environment?: number);
100
101
  /**
101
102
  * @returns {Promise<void>}
102
103
  */
@@ -146,16 +147,19 @@ export declare class TestOpsReporter extends AbstractReporter {
146
147
  private getRelation;
147
148
  /**
148
149
  * @param {TestStepType[]} steps
150
+ * @param testTitle
149
151
  * @returns Promise<ResultStep[]>
150
152
  * @private
151
153
  */
152
154
  private transformSteps;
153
155
  /**
154
156
  * @param {TestStepType[]} steps
157
+ * @param testTitle
155
158
  * @returns Promise<TestStepResultCreate[]>
156
159
  * @private
157
160
  */
158
161
  private transformStepsV1;
162
+ private logEmptyStep;
159
163
  /**
160
164
  * @param {number} runId
161
165
  * @returns {Promise<void>}
@@ -17,14 +17,14 @@ const defaultChunkSize = 200;
17
17
  */
18
18
  class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
19
19
  /**
20
+ * @param {LoggerInterface} logger
20
21
  * @param {ReporterOptionsType & TestOpsOptionsType} options
21
22
  * @param {QaseApiInterface} api
22
- * @param {LoggerInterface} logger
23
23
  * @param {number} environment
24
24
  */
25
- constructor(options, api, logger, environment) {
26
- const { project, uploadAttachments, run, ...restOptions } = options;
27
- super(restOptions, logger);
25
+ constructor(logger, options, api, environment) {
26
+ const { project, uploadAttachments, run, } = options;
27
+ super(logger);
28
28
  this.api = api;
29
29
  /**
30
30
  * @type {number}
@@ -72,15 +72,19 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
72
72
  */
73
73
  async checkOrCreateTestRun() {
74
74
  if (this.run.id !== undefined) {
75
+ this.logger.logDebug('Check test run');
75
76
  await this.checkRun(this.run.id);
76
77
  this.isTestRunReady = true;
77
78
  return;
78
79
  }
80
+ this.logger.logDebug('Create test run');
79
81
  const { result } = await this.createRun(this.run.title, this.run.description, this.environment);
80
82
  if (!result?.id) {
81
83
  throw new Error('Cannot create run.');
82
84
  }
85
+ this.logger.logDebug(`Test run created: ${result.id}`);
83
86
  this.run.id = result.id;
87
+ process.env['QASE_TESTOPS_RUN_ID'] = String(result.id);
84
88
  this.isTestRunReady = true;
85
89
  }
86
90
  /**
@@ -109,14 +113,14 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
109
113
  results: results,
110
114
  });
111
115
  }
112
- this.log((0, chalk_1.default) `{green ${testResults.length} result(s) sent to Qase}`);
116
+ this.logger.logDebug(`Results sent to Qase: ${testResults.length}`);
113
117
  }
114
118
  /**
115
119
  * @returns {Promise<void>}
116
120
  */
117
121
  async publish() {
118
122
  if (this.results.length === 0) {
119
- this.log((0, chalk_1.default) `{yellow No results to send to Qase}`);
123
+ this.logger.log((0, chalk_1.default) `{yellow No results to send to Qase}`);
120
124
  return;
121
125
  }
122
126
  if (this.firstIndex < this.results.length) {
@@ -127,13 +131,13 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
127
131
  }
128
132
  try {
129
133
  await this.api.runs.completeRun(this.projectCode, this.run.id);
130
- this.log((0, chalk_1.default) `{green Run ${this.run.id} completed}`);
134
+ this.logger.log((0, chalk_1.default) `{green Run ${this.run.id} completed}`);
131
135
  }
132
136
  catch (error) {
133
137
  throw new qase_error_1.QaseError('Error on completing run', { cause: error });
134
138
  }
135
139
  const runUrl = `${this.baseUrl}/run/${this.projectCode}/dashboard/${this.run.id}`;
136
- this.log((0, chalk_1.default) `{blue Test run link: ${runUrl}}`);
140
+ this.logger.log((0, chalk_1.default) `{blue Test run link: ${runUrl}}`);
137
141
  }
138
142
  /**
139
143
  * @param {TestResultType} result
@@ -142,8 +146,8 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
142
146
  */
143
147
  async transformTestResult(result) {
144
148
  const attachments = await this.uploadAttachments(result.attachments);
145
- const steps = await this.transformSteps(result.steps);
146
- return {
149
+ const steps = await this.transformSteps(result.steps, result.title);
150
+ const model = {
147
151
  title: result.title,
148
152
  execution: this.getExecution(result.execution),
149
153
  testops_id: Array.isArray(result.testops_id) ? null : result.testops_id,
@@ -153,6 +157,8 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
153
157
  relations: this.getRelation(result.relations),
154
158
  message: result.message,
155
159
  };
160
+ this.logger.logDebug(`Transformed result: ${JSON.stringify(model)}`);
161
+ return model;
156
162
  }
157
163
  /**
158
164
  * @param {TestResultType} result
@@ -161,7 +167,7 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
161
167
  */
162
168
  async transformTestResultV1(result) {
163
169
  const attachments = await this.uploadAttachments(result.attachments);
164
- const steps = await this.transformStepsV1(result.steps);
170
+ const steps = await this.transformStepsV1(result.steps, result.title);
165
171
  const resultCreate = {
166
172
  attachments: attachments,
167
173
  comment: result.message,
@@ -183,6 +189,7 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
183
189
  title: result.title,
184
190
  suite_title: result.relations?.suite ? result.relations?.suite?.data.map((suite) => suite.title).join('\t') : null,
185
191
  };
192
+ this.logger.logDebug(`Transformed result: ${JSON.stringify(resultCreate)}`);
186
193
  return resultCreate;
187
194
  }
188
195
  /**
@@ -224,24 +231,41 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
224
231
  }
225
232
  /**
226
233
  * @param {TestStepType[]} steps
234
+ * @param testTitle
227
235
  * @returns Promise<ResultStep[]>
228
236
  * @private
229
237
  */
230
- async transformSteps(steps) {
238
+ async transformSteps(steps, testTitle) {
231
239
  const resultsSteps = [];
232
240
  for (const step of steps) {
233
241
  const attachmentHashes = await this.uploadAttachments(step.attachments);
234
242
  const resultStep = {
235
243
  data: {
236
- action: step.data.action,
237
- attachments: attachmentHashes,
244
+ action: '',
238
245
  },
239
246
  execution: {
240
247
  status: TestOpsReporter.stepStatusMap[step.execution.status],
248
+ attachments: attachmentHashes,
241
249
  },
242
250
  };
251
+ if (step.step_type === models_1.StepType.TEXT) {
252
+ if ('action' in step.data && resultStep.data != undefined) {
253
+ if (step.data.action === '') {
254
+ this.logEmptyStep(testTitle);
255
+ resultStep.data.action = 'Unnamed step';
256
+ }
257
+ else {
258
+ resultStep.data.action = step.data.action;
259
+ }
260
+ }
261
+ }
262
+ if (step.step_type === models_1.StepType.GHERKIN) {
263
+ if ('keyword' in step.data && resultStep.data != undefined) {
264
+ resultStep.data.action = step.data.keyword;
265
+ }
266
+ }
243
267
  if (step.steps.length > 0) {
244
- resultStep.steps = await this.transformSteps(step.steps);
268
+ resultStep.steps = await this.transformSteps(step.steps, testTitle);
245
269
  }
246
270
  resultsSteps.push(resultStep);
247
271
  }
@@ -249,25 +273,44 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
249
273
  }
250
274
  /**
251
275
  * @param {TestStepType[]} steps
276
+ * @param testTitle
252
277
  * @returns Promise<TestStepResultCreate[]>
253
278
  * @private
254
279
  */
255
- async transformStepsV1(steps) {
280
+ async transformStepsV1(steps, testTitle) {
256
281
  const resultsSteps = [];
257
282
  for (const step of steps) {
258
283
  const attachmentHashes = await this.uploadAttachments(step.attachments);
259
284
  const resultStep = {
260
285
  status: TestOpsReporter.stepStatusMapV1[step.execution.status],
261
- action: step.data.action,
262
286
  attachments: attachmentHashes,
263
287
  };
288
+ if (step.step_type === models_1.StepType.TEXT) {
289
+ if ('action' in step.data) {
290
+ if (step.data.action === '') {
291
+ this.logEmptyStep(testTitle);
292
+ resultStep.action = 'Unnamed step';
293
+ }
294
+ else {
295
+ resultStep.action = step.data.action;
296
+ }
297
+ }
298
+ }
299
+ if (step.step_type === models_1.StepType.GHERKIN) {
300
+ if ('keyword' in step.data) {
301
+ resultStep.action = step.data.keyword;
302
+ }
303
+ }
264
304
  if (step.steps.length > 0) {
265
- resultStep.steps = await this.transformStepsV1(step.steps);
305
+ resultStep.steps = await this.transformStepsV1(step.steps, testTitle);
266
306
  }
267
307
  resultsSteps.push(resultStep);
268
308
  }
269
309
  return resultsSteps;
270
310
  }
311
+ logEmptyStep(testTitle) {
312
+ this.logger.log((0, chalk_1.default) `{magenta Test '${testTitle}' has empty action in step. The reporter will mark this step as unnamed step.}`);
313
+ }
271
314
  /**
272
315
  * @param {number} runId
273
316
  * @returns {Promise<void>}
@@ -276,7 +319,7 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
276
319
  async checkRun(runId) {
277
320
  try {
278
321
  const resp = await this.api.runs.getRun(this.projectCode, runId);
279
- this.log(`Get run result on checking run "${String(resp.data.result?.id)}"`);
322
+ this.logger.log(`Get run result on checking run "${String(resp.data.result?.id)}"`);
280
323
  }
281
324
  catch (error) {
282
325
  throw new qase_error_1.QaseError('Error on checking run', { cause: error });
@@ -336,7 +379,7 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
336
379
  }
337
380
  }
338
381
  catch (error) {
339
- this.logError('Cannot upload attachment:', error);
382
+ this.logger.logError('Cannot upload attachment:', error);
340
383
  }
341
384
  }
342
385
  return acc;
@@ -0,0 +1,29 @@
1
+ export interface LoggerInterface {
2
+ log(message: string): void;
3
+ logError(message: string, error?: unknown): void;
4
+ logDebug(message: string): void;
5
+ }
6
+ export declare class Logger implements LoggerInterface {
7
+ private readonly debug;
8
+ private readonly filePath;
9
+ constructor(options: {
10
+ debug?: boolean | undefined;
11
+ dir?: string;
12
+ });
13
+ log(message: string): void;
14
+ logError(message: string, error?: unknown): void;
15
+ logDebug(message: string): void;
16
+ private logToFile;
17
+ private doLogError;
18
+ /**
19
+ * @param {AxiosError} error
20
+ * @private
21
+ */
22
+ private logApiError;
23
+ /**
24
+ * @param errorFields
25
+ * @returns {string | undefined}
26
+ * @private
27
+ */
28
+ private formatErrorFields;
29
+ }
@@ -0,0 +1,121 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.Logger = void 0;
30
+ const fs = __importStar(require("fs"));
31
+ const path = __importStar(require("path"));
32
+ const is_axios_error_1 = require("./is-axios-error");
33
+ const qase_error_1 = require("./qase-error");
34
+ const lodash_get_1 = __importDefault(require("lodash.get"));
35
+ class Logger {
36
+ constructor(options) {
37
+ this.debug = options.debug;
38
+ const dir = options.dir ?? './logs';
39
+ if (!fs.existsSync(dir)) {
40
+ fs.mkdirSync(dir);
41
+ }
42
+ this.filePath = path.join(dir, 'log.txt');
43
+ }
44
+ log(message) {
45
+ const logMessage = `[INFO] qase: ${message}`;
46
+ console.log(logMessage);
47
+ if (this.debug) {
48
+ this.logToFile(logMessage);
49
+ }
50
+ }
51
+ logError(message, error) {
52
+ const logMessage = `[ERROR] qase: ${this.doLogError(message, error)}`;
53
+ console.error(logMessage);
54
+ if (this.debug) {
55
+ this.logToFile(logMessage);
56
+ }
57
+ }
58
+ logDebug(message) {
59
+ if (this.debug) {
60
+ const logMessage = `[DEBUG] qase: ${message}`;
61
+ console.log(logMessage);
62
+ this.logToFile(logMessage);
63
+ }
64
+ }
65
+ logToFile(message) {
66
+ const formattedMessage = `[${new Date().toISOString()}] ${message}\n`;
67
+ fs.appendFileSync(this.filePath, formattedMessage);
68
+ }
69
+ doLogError(message, error) {
70
+ let logMessage = message;
71
+ if (error instanceof Error) {
72
+ if ((0, is_axios_error_1.isAxiosError)(error)) {
73
+ logMessage += this.logApiError(error);
74
+ }
75
+ else if (error instanceof qase_error_1.QaseError && error.cause) {
76
+ logMessage += this.doLogError('\n Caused by:', error.cause);
77
+ }
78
+ logMessage += `\n ${error.stack || `${error.name}: ${error.message}`}`;
79
+ }
80
+ else {
81
+ logMessage += `\n ${String(error)}`;
82
+ }
83
+ return logMessage;
84
+ }
85
+ /**
86
+ * @param {AxiosError} error
87
+ * @private
88
+ */
89
+ logApiError(error) {
90
+ let logMessage = '\n';
91
+ const errorMessage = (0, lodash_get_1.default)(error, 'response.data.errorMessage')
92
+ ?? (0, lodash_get_1.default)(error, 'response.data.error')
93
+ ?? (0, lodash_get_1.default)(error, 'response.statusText')
94
+ ?? 'Unknown error';
95
+ const errorFields = this.formatErrorFields((0, lodash_get_1.default)(error, 'response.data.errorFields'));
96
+ logMessage += `Message: ${String(errorMessage)}`;
97
+ if (errorFields) {
98
+ logMessage += `\n ${errorFields}`;
99
+ }
100
+ return logMessage;
101
+ }
102
+ /**
103
+ * @param errorFields
104
+ * @returns {string | undefined}
105
+ * @private
106
+ */
107
+ formatErrorFields(errorFields) {
108
+ if (Array.isArray(errorFields)) {
109
+ return errorFields.reduce((acc, item) => {
110
+ const field = (0, lodash_get_1.default)(item, 'field');
111
+ const error = (0, lodash_get_1.default)(item, 'error');
112
+ if (field && error) {
113
+ return acc + `${String(field)}: ${String(error)}\n`;
114
+ }
115
+ return acc;
116
+ }, '');
117
+ }
118
+ return undefined;
119
+ }
120
+ }
121
+ exports.Logger = Logger;
@@ -17,6 +17,10 @@ export declare class FsWriter implements WriterInterface {
17
17
  * @param {FsWriterOptionsType | undefined} options
18
18
  */
19
19
  constructor(options: FsWriterOptionsType | undefined);
20
+ /**
21
+ * @returns {void}
22
+ */
23
+ clearPreviousResults(): void;
20
24
  /**
21
25
  * @param {Attachment[]} attachments
22
26
  * @returns {Attachment[]}
@@ -32,4 +36,5 @@ export declare class FsWriter implements WriterInterface {
32
36
  * @param {TestResultType} result
33
37
  */
34
38
  writeTestResult(result: TestResultType): Promise<void>;
39
+ private deleteFolderRecursive;
35
40
  }
@@ -47,6 +47,12 @@ class FsWriter {
47
47
  this.formatter = new formatter_1.JsonpFormatter();
48
48
  }
49
49
  }
50
+ /**
51
+ * @returns {void}
52
+ */
53
+ clearPreviousResults() {
54
+ this.deleteFolderRecursive(this.path);
55
+ }
50
56
  /**
51
57
  * @param {Attachment[]} attachments
52
58
  * @returns {Attachment[]}
@@ -102,5 +108,19 @@ class FsWriter {
102
108
  const filePath = path.join(resultsPath, `${result.id}.${this.format}`);
103
109
  (0, fs_1.writeFileSync)(filePath, await this.formatter.format(result));
104
110
  }
111
+ deleteFolderRecursive(directoryPath) {
112
+ if ((0, fs_1.existsSync)(directoryPath)) {
113
+ (0, fs_1.readdirSync)(directoryPath).forEach((file) => {
114
+ const curPath = path.join(directoryPath, file);
115
+ if ((0, fs_1.lstatSync)(curPath).isDirectory()) { // recurse
116
+ this.deleteFolderRecursive(curPath);
117
+ }
118
+ else { // delete file
119
+ (0, fs_1.unlinkSync)(curPath);
120
+ }
121
+ });
122
+ (0, fs_1.rmdirSync)(directoryPath);
123
+ }
124
+ }
105
125
  }
106
126
  exports.FsWriter = FsWriter;
@@ -3,4 +3,5 @@ export interface WriterInterface {
3
3
  writeReport(results: Report): Promise<string>;
4
4
  writeTestResult(result: TestResultType): Promise<void>;
5
5
  writeAttachment(attachments: Attachment[]): Attachment[];
6
+ clearPreviousResults(): void;
6
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qase-javascript-commons",
3
- "version": "2.0.0-beta.8",
3
+ "version": "2.0.0",
4
4
  "description": "Qase JS Reporters",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "test": "jest --passWithNoTests",
22
22
  "clean": "rm -rf dist"
23
23
  },
24
- "author": "Dimitri Harding <dharding@dimitriharding.com>",
24
+ "author": "Qase Team <dharding@dimitriharding.com>",
25
25
  "license": "Apache-2.0",
26
26
  "dependencies": {
27
27
  "ajv": "^8.12.0",
@@ -33,7 +33,7 @@
33
33
  "lodash.merge": "^4.6.2",
34
34
  "lodash.mergewith": "^4.6.2",
35
35
  "mime-types": "^2.1.33",
36
- "qaseio": "^2.1.0-beta.1",
36
+ "qaseio": "^2.1.0",
37
37
  "strip-ansi": "^6.0.1",
38
38
  "uuid": "^9.0.0"
39
39
  },