qase-javascript-commons 2.0.0-beta.8 → 2.0.0-beta.9

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/changelog.md CHANGED
@@ -1,3 +1,25 @@
1
+ # qase-javascript-commons@2.0.0-beta.9
2
+
3
+ ## What's new
4
+
5
+ Improved debug logging for better testing and reporting errors.
6
+
7
+ - Separate `logger` class for use in reporters, supporting logging to console and files.
8
+ - Extra debug logs in both reporter modes: TestOps and Local.
9
+
10
+ Fixed an issue with duplicate test runs created when the testing framework
11
+ (such as Cypress) uses more than one instance of the Qase reporter.
12
+ Now reporter handles Qase test runs in the following way:
13
+
14
+ 1. The first instance of the reporter creates a Qase test run and stores the run ID
15
+ in the ENV variable `QASE_TESTOPS_RUN_ID`.
16
+ 2. Other instances of the reporter read this variable and report test results
17
+ to the existing test run.
18
+
19
+ Nothing has changed in cases when there is a single instance of a reporter or
20
+ when it is using a test run, created with other tools, such as with an API request
21
+ or manually in the Qase app.
22
+
1
23
  # qase-javascript-commons@2.0.0-beta.8
2
24
 
3
25
  ## 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,4 +1,4 @@
1
- import { AbstractReporter, LoggerInterface } from './reporters';
1
+ import { AbstractReporter } from './reporters';
2
2
  import { OptionsType } from './options';
3
3
  import { TestResultType } from './models';
4
4
  /**
@@ -36,9 +36,8 @@ export declare class QaseReporter extends AbstractReporter {
36
36
  private useFallback;
37
37
  /**
38
38
  * @param {OptionsType} options
39
- * @param {LoggerInterface} logger
40
39
  */
41
- constructor(options: OptionsType, logger?: LoggerInterface);
40
+ constructor(options: OptionsType);
42
41
  /**
43
42
  * @returns {Promise<void>}
44
43
  */
@@ -64,7 +63,6 @@ export declare class QaseReporter extends AbstractReporter {
64
63
  * @todo implement mode registry
65
64
  * @param {ModeEnum} mode
66
65
  * @param {OptionsType} options
67
- * @param {LoggerInterface} logger
68
66
  * @returns {ReporterInterface}
69
67
  * @private
70
68
  */
package/dist/qase.js CHANGED
@@ -66,12 +66,11 @@ class QaseReporter extends reporters_1.AbstractReporter {
66
66
  }
67
67
  /**
68
68
  * @param {OptionsType} options
69
- * @param {LoggerInterface} logger
70
69
  */
71
- constructor(options, logger) {
70
+ constructor(options) {
72
71
  const env = (0, env_1.envToConfig)((0, env_schema_1.default)({ schema: env_1.envValidationSchema }));
73
72
  const composedOptions = (0, options_1.composeOptions)(options, env);
74
- super({ debug: composedOptions.debug, captureLogs: composedOptions.captureLogs }, logger);
73
+ super({ debug: composedOptions.debug, captureLogs: composedOptions.captureLogs });
75
74
  /**
76
75
  * @type {boolean}
77
76
  * @private
@@ -82,17 +81,18 @@ class QaseReporter extends reporters_1.AbstractReporter {
82
81
  * @private
83
82
  */
84
83
  this.useFallback = false;
84
+ this.logger.logDebug(`Config: ${JSON.stringify(composedOptions)}`);
85
85
  try {
86
86
  this.upstreamReporter = this.createReporter(
87
87
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
88
- composedOptions.mode || options_1.ModeEnum.off, composedOptions, logger);
88
+ composedOptions.mode || options_1.ModeEnum.off, composedOptions);
89
89
  }
90
90
  catch (error) {
91
91
  if (error instanceof disabled_exception_1.DisabledException) {
92
92
  this.disabled = true;
93
93
  }
94
94
  else {
95
- this.logError('Unable to create upstream reporter:', error);
95
+ this.logger.logError('Unable to create upstream reporter:', error);
96
96
  if (composedOptions.fallback != undefined) {
97
97
  this.disabled = true;
98
98
  return;
@@ -103,7 +103,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
103
103
  try {
104
104
  this.fallbackReporter = this.createReporter(
105
105
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
106
- composedOptions.fallback || options_1.ModeEnum.off, composedOptions, logger);
106
+ composedOptions.fallback || options_1.ModeEnum.off, composedOptions);
107
107
  }
108
108
  catch (error) {
109
109
  if (error instanceof disabled_exception_1.DisabledException) {
@@ -112,7 +112,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
112
112
  }
113
113
  }
114
114
  else {
115
- this.logError('Unable to create fallback reporter:', error);
115
+ this.logger.logError('Unable to create fallback reporter:', error);
116
116
  if (this.useFallback && this.upstreamReporter === undefined) {
117
117
  this.disabled = true;
118
118
  }
@@ -124,11 +124,12 @@ class QaseReporter extends reporters_1.AbstractReporter {
124
124
  */
125
125
  async startTestRun() {
126
126
  if (!this.disabled) {
127
+ this.logger.logDebug('Starting test run');
127
128
  try {
128
129
  await this.upstreamReporter?.startTestRun();
129
130
  }
130
131
  catch (error) {
131
- this.logError('Unable to start test run in the upstream reporter: ', error);
132
+ this.logger.logError('Unable to start test run in the upstream reporter: ', error);
132
133
  if (this.fallbackReporter == undefined) {
133
134
  this.disabled = true;
134
135
  return;
@@ -137,7 +138,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
137
138
  await this.fallbackReporter?.startTestRun();
138
139
  }
139
140
  catch (error) {
140
- this.logError('Unable to start test run in the fallback reporter: ', error);
141
+ this.logger.logError('Unable to start test run in the fallback reporter: ', error);
141
142
  this.disabled = true;
142
143
  }
143
144
  }
@@ -157,7 +158,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
157
158
  await this.upstreamReporter?.addTestResult(result);
158
159
  }
159
160
  catch (error) {
160
- this.logError('Unable to add the result to the upstream reporter:', error);
161
+ this.logger.logError('Unable to add the result to the upstream reporter:', error);
161
162
  if (this.fallbackReporter == undefined) {
162
163
  this.disabled = true;
163
164
  return;
@@ -179,7 +180,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
179
180
  await this.fallbackReporter?.addTestResult(result);
180
181
  }
181
182
  catch (error) {
182
- this.logError('Unable to add the result to the fallback reporter:', error);
183
+ this.logger.logError('Unable to add the result to the fallback reporter:', error);
183
184
  this.disabled = true;
184
185
  }
185
186
  }
@@ -188,6 +189,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
188
189
  */
189
190
  async publish() {
190
191
  if (!this.disabled) {
192
+ this.logger.logDebug('Publishing test run results');
191
193
  if (this.useFallback) {
192
194
  await this.publishFallback();
193
195
  }
@@ -195,7 +197,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
195
197
  await this.upstreamReporter?.publish();
196
198
  }
197
199
  catch (error) {
198
- this.logError('Unable to publish the run results to the upstream reporter:', error);
200
+ this.logger.logError('Unable to publish the run results to the upstream reporter:', error);
199
201
  if (this.fallbackReporter == undefined) {
200
202
  this.disabled = true;
201
203
  return;
@@ -216,7 +218,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
216
218
  await this.fallbackReporter?.publish();
217
219
  }
218
220
  catch (error) {
219
- this.logError('Unable to publish the run results to the fallback reporter:', error);
221
+ this.logger.logError('Unable to publish the run results to the fallback reporter:', error);
220
222
  this.disabled = true;
221
223
  }
222
224
  }
@@ -224,11 +226,10 @@ class QaseReporter extends reporters_1.AbstractReporter {
224
226
  * @todo implement mode registry
225
227
  * @param {ModeEnum} mode
226
228
  * @param {OptionsType} options
227
- * @param {LoggerInterface} logger
228
229
  * @returns {ReporterInterface}
229
230
  * @private
230
231
  */
231
- createReporter(mode, options, logger) {
232
+ createReporter(mode, options) {
232
233
  const { frameworkPackage, frameworkName, reporterName, environment, report = {}, testops = {}, ...commonOptions } = options;
233
234
  switch (mode) {
234
235
  case options_1.ModeEnum.testops: {
@@ -259,7 +260,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
259
260
  batch,
260
261
  debug: commonOptions.debug,
261
262
  captureLogs: commonOptions.captureLogs,
262
- }, apiClient, logger, typeof environment === 'number' ? environment : undefined);
263
+ }, apiClient, typeof environment === 'number' ? environment : undefined);
263
264
  }
264
265
  case options_1.ModeEnum.report: {
265
266
  const localOptions = report.connections?.[writer_1.DriverEnum.local];
@@ -267,7 +268,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
267
268
  return new reporters_1.ReportReporter({
268
269
  debug: commonOptions.debug,
269
270
  captureLogs: commonOptions.captureLogs,
270
- }, writer, logger, typeof environment === 'number' ? environment.toString() : environment, testops.run?.id);
271
+ }, writer, typeof environment === 'number' ? environment.toString() : environment, testops.run?.id);
271
272
  }
272
273
  case options_1.ModeEnum.off:
273
274
  throw new disabled_exception_1.DisabledException();
@@ -280,7 +281,7 @@ class QaseReporter extends reporters_1.AbstractReporter {
280
281
  * @private
281
282
  */
282
283
  logTestItem(test) {
283
- this.log(resultLogMap[test.execution.status](test));
284
+ this.logger.log(resultLogMap[test.execution.status](test));
284
285
  }
285
286
  }
286
287
  exports.QaseReporter = QaseReporter;
@@ -1,8 +1,8 @@
1
1
  import { TestResultType } from '../models';
2
2
  export interface LoggerInterface {
3
3
  log(message: string): void;
4
- group(): void;
5
- groupEnd(): void;
4
+ logError(message: string, error?: unknown): void;
5
+ logDebug(message: string): void;
6
6
  }
7
7
  export interface ReporterOptionsType {
8
8
  debug?: boolean | undefined;
@@ -22,17 +22,16 @@ export interface ReporterInterface {
22
22
  * @implements ReporterInterface
23
23
  */
24
24
  export declare abstract class AbstractReporter implements ReporterInterface {
25
- private logger;
26
25
  /**
27
26
  * @type {boolean | undefined}
28
27
  * @private
29
28
  */
30
- private readonly debug;
29
+ private readonly captureLogs;
31
30
  /**
32
- * @type {boolean | undefined}
31
+ * @type {LoggerInterface}
33
32
  * @private
34
33
  */
35
- private readonly captureLogs;
34
+ protected readonly logger: LoggerInterface;
36
35
  /**
37
36
  * @type {TestResultType[]}
38
37
  * @protected
@@ -48,10 +47,9 @@ export declare abstract class AbstractReporter implements ReporterInterface {
48
47
  abstract startTestRun(): Promise<void>;
49
48
  /**
50
49
  * @param {ReporterOptionsType} options
51
- * @param {LoggerInterface} logger
52
50
  * @protected
53
51
  */
54
- protected constructor(options: ReporterOptionsType | undefined, logger?: LoggerInterface);
52
+ protected constructor(options: ReporterOptionsType | undefined);
55
53
  /**
56
54
  * @returns {TestResultType[]}
57
55
  */
@@ -68,32 +66,4 @@ export declare abstract class AbstractReporter implements ReporterInterface {
68
66
  * @param {TestResultType[]} results
69
67
  */
70
68
  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
69
  }
@@ -1,13 +1,8 @@
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");
5
+ const logger_1 = require("../utils/logger");
11
6
  /**
12
7
  * @abstract
13
8
  * @class AbstractReporter
@@ -16,19 +11,17 @@ const uuid_1 = require("uuid");
16
11
  class AbstractReporter {
17
12
  /**
18
13
  * @param {ReporterOptionsType} options
19
- * @param {LoggerInterface} logger
20
14
  * @protected
21
15
  */
22
- constructor(options, logger = console) {
23
- this.logger = logger;
16
+ constructor(options) {
24
17
  /**
25
18
  * @type {TestResultType[]}
26
19
  * @protected
27
20
  */
28
21
  this.results = [];
29
22
  const { debug, captureLogs } = options ?? {};
30
- this.debug = debug;
31
23
  this.captureLogs = captureLogs;
24
+ this.logger = new logger_1.Logger({ debug });
32
25
  }
33
26
  /**
34
27
  * @returns {TestResultType[]}
@@ -47,6 +40,7 @@ class AbstractReporter {
47
40
  */
48
41
  // eslint-disable-next-line @typescript-eslint/require-await
49
42
  async addTestResult(result) {
43
+ this.logger.logDebug(`Adding test result: ${JSON.stringify(result)}`);
50
44
  if (result.testops_id === null || !Array.isArray(result.testops_id)) {
51
45
  this.results.push(result);
52
46
  return;
@@ -70,79 +64,5 @@ class AbstractReporter {
70
64
  setTestResults(results) {
71
65
  this.results = results;
72
66
  }
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
67
  }
148
68
  exports.AbstractReporter = AbstractReporter;
@@ -1,4 +1,4 @@
1
- import { AbstractReporter, LoggerInterface, ReporterOptionsType } from './abstract-reporter';
1
+ import { AbstractReporter, ReporterOptionsType } from './abstract-reporter';
2
2
  import { WriterInterface } from '../writer';
3
3
  /**
4
4
  * @class ReportReporter
@@ -12,11 +12,10 @@ export declare class ReportReporter extends AbstractReporter {
12
12
  /**
13
13
  * @param {ReporterOptionsType} options
14
14
  * @param {WriterInterface} writer
15
- * @param {LoggerInterface} logger
16
15
  * @param {string | undefined} environment
17
16
  * @param {number | undefined} runId
18
17
  */
19
- constructor(options: ReporterOptionsType | undefined, writer: WriterInterface, logger?: LoggerInterface, environment?: string, runId?: number);
18
+ constructor(options: ReporterOptionsType | undefined, writer: WriterInterface, environment?: string, runId?: number);
20
19
  /**
21
20
  * @returns {Promise<void>}
22
21
  */
@@ -37,12 +37,11 @@ class ReportReporter extends abstract_reporter_1.AbstractReporter {
37
37
  /**
38
38
  * @param {ReporterOptionsType} options
39
39
  * @param {WriterInterface} writer
40
- * @param {LoggerInterface} logger
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(options, writer, environment, runId) {
44
+ super(options);
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,5 +1,5 @@
1
1
  import { QaseApiInterface, ResultStepStatus, TestStepResultCreateStatusEnum } from 'qaseio';
2
- import { AbstractReporter, LoggerInterface, ReporterOptionsType } from './abstract-reporter';
2
+ import { AbstractReporter, ReporterOptionsType } from './abstract-reporter';
3
3
  import { StepStatusEnum, TestResultType, TestStatusEnum } from '../models';
4
4
  export type TestOpsRunType = {
5
5
  id?: number | undefined;
@@ -93,10 +93,9 @@ export declare class TestOpsReporter extends AbstractReporter {
93
93
  /**
94
94
  * @param {ReporterOptionsType & TestOpsOptionsType} options
95
95
  * @param {QaseApiInterface} api
96
- * @param {LoggerInterface} logger
97
96
  * @param {number} environment
98
97
  */
99
- constructor(options: ReporterOptionsType & TestOpsOptionsType, api: QaseApiInterface, logger?: LoggerInterface, environment?: number);
98
+ constructor(options: ReporterOptionsType & TestOpsOptionsType, api: QaseApiInterface, environment?: number);
100
99
  /**
101
100
  * @returns {Promise<void>}
102
101
  */
@@ -19,12 +19,11 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
19
19
  /**
20
20
  * @param {ReporterOptionsType & TestOpsOptionsType} options
21
21
  * @param {QaseApiInterface} api
22
- * @param {LoggerInterface} logger
23
22
  * @param {number} environment
24
23
  */
25
- constructor(options, api, logger, environment) {
24
+ constructor(options, api, environment) {
26
25
  const { project, uploadAttachments, run, ...restOptions } = options;
27
- super(restOptions, logger);
26
+ super(restOptions);
28
27
  this.api = api;
29
28
  /**
30
29
  * @type {number}
@@ -72,15 +71,19 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
72
71
  */
73
72
  async checkOrCreateTestRun() {
74
73
  if (this.run.id !== undefined) {
74
+ this.logger.logDebug('Check test run');
75
75
  await this.checkRun(this.run.id);
76
76
  this.isTestRunReady = true;
77
77
  return;
78
78
  }
79
+ this.logger.logDebug('Create test run');
79
80
  const { result } = await this.createRun(this.run.title, this.run.description, this.environment);
80
81
  if (!result?.id) {
81
82
  throw new Error('Cannot create run.');
82
83
  }
84
+ this.logger.logDebug(`Test run created: ${result.id}`);
83
85
  this.run.id = result.id;
86
+ process.env['QASE_TESTOPS_RUN_ID'] = String(result.id);
84
87
  this.isTestRunReady = true;
85
88
  }
86
89
  /**
@@ -109,14 +112,14 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
109
112
  results: results,
110
113
  });
111
114
  }
112
- this.log((0, chalk_1.default) `{green ${testResults.length} result(s) sent to Qase}`);
115
+ this.logger.logDebug(`Results sent to Qase: ${testResults.length}`);
113
116
  }
114
117
  /**
115
118
  * @returns {Promise<void>}
116
119
  */
117
120
  async publish() {
118
121
  if (this.results.length === 0) {
119
- this.log((0, chalk_1.default) `{yellow No results to send to Qase}`);
122
+ this.logger.log((0, chalk_1.default) `{yellow No results to send to Qase}`);
120
123
  return;
121
124
  }
122
125
  if (this.firstIndex < this.results.length) {
@@ -127,13 +130,13 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
127
130
  }
128
131
  try {
129
132
  await this.api.runs.completeRun(this.projectCode, this.run.id);
130
- this.log((0, chalk_1.default) `{green Run ${this.run.id} completed}`);
133
+ this.logger.log((0, chalk_1.default) `{green Run ${this.run.id} completed}`);
131
134
  }
132
135
  catch (error) {
133
136
  throw new qase_error_1.QaseError('Error on completing run', { cause: error });
134
137
  }
135
138
  const runUrl = `${this.baseUrl}/run/${this.projectCode}/dashboard/${this.run.id}`;
136
- this.log((0, chalk_1.default) `{blue Test run link: ${runUrl}}`);
139
+ this.logger.log((0, chalk_1.default) `{blue Test run link: ${runUrl}}`);
137
140
  }
138
141
  /**
139
142
  * @param {TestResultType} result
@@ -143,7 +146,7 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
143
146
  async transformTestResult(result) {
144
147
  const attachments = await this.uploadAttachments(result.attachments);
145
148
  const steps = await this.transformSteps(result.steps);
146
- return {
149
+ const model = {
147
150
  title: result.title,
148
151
  execution: this.getExecution(result.execution),
149
152
  testops_id: Array.isArray(result.testops_id) ? null : result.testops_id,
@@ -153,6 +156,8 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
153
156
  relations: this.getRelation(result.relations),
154
157
  message: result.message,
155
158
  };
159
+ this.logger.logDebug(`Transformed result: ${JSON.stringify(model)}`);
160
+ return model;
156
161
  }
157
162
  /**
158
163
  * @param {TestResultType} result
@@ -183,6 +188,7 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
183
188
  title: result.title,
184
189
  suite_title: result.relations?.suite ? result.relations?.suite?.data.map((suite) => suite.title).join('\t') : null,
185
190
  };
191
+ this.logger.logDebug(`Transformed result: ${JSON.stringify(resultCreate)}`);
186
192
  return resultCreate;
187
193
  }
188
194
  /**
@@ -233,13 +239,23 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
233
239
  const attachmentHashes = await this.uploadAttachments(step.attachments);
234
240
  const resultStep = {
235
241
  data: {
236
- action: step.data.action,
242
+ action: '',
237
243
  attachments: attachmentHashes,
238
244
  },
239
245
  execution: {
240
246
  status: TestOpsReporter.stepStatusMap[step.execution.status],
241
247
  },
242
248
  };
249
+ if (step.step_type === models_1.StepType.TEXT) {
250
+ if ('action' in step.data && resultStep.data != undefined) {
251
+ resultStep.data.action = step.data.action;
252
+ }
253
+ }
254
+ if (step.step_type === models_1.StepType.GHERKIN) {
255
+ if ('keyword' in step.data && resultStep.data != undefined) {
256
+ resultStep.data.action = step.data.keyword;
257
+ }
258
+ }
243
259
  if (step.steps.length > 0) {
244
260
  resultStep.steps = await this.transformSteps(step.steps);
245
261
  }
@@ -258,9 +274,18 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
258
274
  const attachmentHashes = await this.uploadAttachments(step.attachments);
259
275
  const resultStep = {
260
276
  status: TestOpsReporter.stepStatusMapV1[step.execution.status],
261
- action: step.data.action,
262
277
  attachments: attachmentHashes,
263
278
  };
279
+ if (step.step_type === models_1.StepType.TEXT) {
280
+ if ('action' in step.data) {
281
+ resultStep.action = step.data.action;
282
+ }
283
+ }
284
+ if (step.step_type === models_1.StepType.GHERKIN) {
285
+ if ('keyword' in step.data) {
286
+ resultStep.action = step.data.keyword;
287
+ }
288
+ }
264
289
  if (step.steps.length > 0) {
265
290
  resultStep.steps = await this.transformStepsV1(step.steps);
266
291
  }
@@ -276,7 +301,7 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
276
301
  async checkRun(runId) {
277
302
  try {
278
303
  const resp = await this.api.runs.getRun(this.projectCode, runId);
279
- this.log(`Get run result on checking run "${String(resp.data.result?.id)}"`);
304
+ this.logger.log(`Get run result on checking run "${String(resp.data.result?.id)}"`);
280
305
  }
281
306
  catch (error) {
282
307
  throw new qase_error_1.QaseError('Error on checking run', { cause: error });
@@ -336,7 +361,7 @@ class TestOpsReporter extends abstract_reporter_1.AbstractReporter {
336
361
  }
337
362
  }
338
363
  catch (error) {
339
- this.logError('Cannot upload attachment:', error);
364
+ this.logger.logError('Cannot upload attachment:', error);
340
365
  }
341
366
  }
342
367
  return acc;
@@ -0,0 +1,24 @@
1
+ export declare class Logger {
2
+ private readonly debug;
3
+ private readonly filePath;
4
+ constructor(options: {
5
+ debug?: boolean | undefined;
6
+ dir?: string;
7
+ });
8
+ log(message: string): void;
9
+ logError(message: string, error?: unknown): void;
10
+ logDebug(message: string): void;
11
+ private logToFile;
12
+ private doLogError;
13
+ /**
14
+ * @param {AxiosError} error
15
+ * @private
16
+ */
17
+ private logApiError;
18
+ /**
19
+ * @param errorFields
20
+ * @returns {string | undefined}
21
+ * @private
22
+ */
23
+ private formatErrorFields;
24
+ }
@@ -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-beta.9",
4
4
  "description": "Qase JS Reporters",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",