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 +16 -2
- package/changelog.md +98 -0
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.js +3 -1
- package/dist/models/step-data.d.ts +6 -1
- package/dist/models/test-step.d.ts +7 -3
- package/dist/models/test-step.js +6 -0
- package/dist/qase.d.ts +30 -10
- package/dist/qase.js +50 -34
- package/dist/reporters/abstract-reporter.d.ts +8 -56
- package/dist/reporters/abstract-reporter.js +5 -94
- package/dist/reporters/index.d.ts +1 -1
- package/dist/reporters/report-reporter.d.ts +4 -4
- package/dist/reporters/report-reporter.js +5 -5
- package/dist/reporters/testops-reporter.d.ts +7 -3
- package/dist/reporters/testops-reporter.js +63 -20
- package/dist/utils/logger.d.ts +29 -0
- package/dist/utils/logger.js +121 -0
- package/dist/writer/fs-writer.d.ts +5 -0
- package/dist/writer/fs-writer.js +20 -0
- package/dist/writer/writer-interface.d.ts +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
# Qase JavaScript Commons
|
|
2
2
|
|
|
3
|
-
This
|
|
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
|
|
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
|
package/dist/models/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/models/index.js
CHANGED
|
@@ -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,10 +1,14 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
7
|
-
data:
|
|
10
|
+
step_type: StepType;
|
|
11
|
+
data: StepTextData | StepGherkinData;
|
|
8
12
|
parent_id: string | null;
|
|
9
13
|
execution: StepExecution;
|
|
10
14
|
attachments: Attachment[];
|
package/dist/models/test-step.js
CHANGED
|
@@ -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
|
|
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 {
|
|
24
|
+
* @type {InternalReporterInterface}
|
|
19
25
|
* @private
|
|
20
26
|
*/
|
|
21
27
|
private readonly upstreamReporter?;
|
|
22
28
|
/**
|
|
23
|
-
* @type {
|
|
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(
|
|
53
|
+
private constructor();
|
|
42
54
|
/**
|
|
43
|
-
* @returns {
|
|
55
|
+
* @returns {void}
|
|
56
|
+
*/
|
|
57
|
+
startTestRun(): void;
|
|
58
|
+
/**
|
|
59
|
+
* @param {OptionsType} options
|
|
60
|
+
* @returns {QaseReporter}
|
|
44
61
|
*/
|
|
45
|
-
|
|
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
|
-
* @
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
125
|
+
* @returns {void}
|
|
124
126
|
*/
|
|
125
|
-
|
|
127
|
+
startTestRun() {
|
|
126
128
|
if (!this.disabled) {
|
|
129
|
+
this.logger.logDebug('Starting test run');
|
|
127
130
|
try {
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
* @
|
|
228
|
-
* @returns {ReporterInterface}
|
|
248
|
+
* @returns {InternalReporterInterface}
|
|
229
249
|
* @private
|
|
230
250
|
*/
|
|
231
|
-
createReporter(mode, options
|
|
232
|
-
const { frameworkPackage, frameworkName, reporterName, environment, report = {}, testops = {},
|
|
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
|
-
|
|
261
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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
|
|
13
|
+
* @implements InternalReporterInterface
|
|
23
14
|
*/
|
|
24
|
-
export declare abstract class AbstractReporter implements
|
|
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 {
|
|
17
|
+
* @type {LoggerInterface}
|
|
33
18
|
* @private
|
|
34
19
|
*/
|
|
35
|
-
|
|
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(
|
|
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
|
|
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(
|
|
23
|
-
this.logger = logger;
|
|
15
|
+
constructor(logger) {
|
|
24
16
|
/**
|
|
25
17
|
* @type {TestResultType[]}
|
|
26
18
|
* @protected
|
|
27
19
|
*/
|
|
28
20
|
this.results = [];
|
|
29
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
45
|
-
super(
|
|
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
|
|
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(
|
|
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,
|
|
26
|
-
const { project, uploadAttachments, run,
|
|
27
|
-
super(
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
}
|
package/dist/writer/fs-writer.js
CHANGED
|
@@ -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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qase-javascript-commons",
|
|
3
|
-
"version": "2.0.0
|
|
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": "
|
|
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
|
|
36
|
+
"qaseio": "^2.1.0",
|
|
37
37
|
"strip-ansi": "^6.0.1",
|
|
38
38
|
"uuid": "^9.0.0"
|
|
39
39
|
},
|