qase-javascript-commons 2.2.19 → 2.3.1
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 +24 -24
- package/changelog.md +21 -15
- package/dist/client/clientV1.d.ts +31 -0
- package/dist/client/clientV1.js +177 -0
- package/dist/client/clientV2.d.ts +26 -0
- package/dist/client/clientV2.js +197 -0
- package/dist/client/dateUtils.d.ts +2 -0
- package/dist/client/dateUtils.js +20 -0
- package/dist/client/interface.d.ts +6 -0
- package/dist/client/interface.js +2 -0
- package/dist/config/config-validation-schema.js +0 -22
- package/dist/env/env-enum.d.ts +1 -2
- package/dist/env/env-enum.js +0 -1
- package/dist/env/env-to-config.js +0 -1
- package/dist/env/env-type.d.ts +2 -3
- package/dist/env/env-validation-schema.js +0 -4
- package/dist/formatter/json-formatter.d.ts +2 -2
- package/dist/formatter/jsonp-formatter.d.ts +2 -2
- package/dist/models/attachment.d.ts +2 -2
- package/dist/models/config/TestOpsOptionsType.d.ts +25 -0
- package/dist/models/config/TestOpsOptionsType.js +2 -0
- package/dist/models/execution-sum.d.ts +2 -2
- package/dist/models/host-data.d.ts +2 -1
- package/dist/models/report.d.ts +3 -3
- package/dist/models/short-result.d.ts +2 -2
- package/dist/models/stats.d.ts +2 -2
- package/dist/models/test-result.d.ts +6 -6
- package/dist/options/options-type.d.ts +2 -6
- package/dist/qase.d.ts +1 -0
- package/dist/qase.js +16 -53
- package/dist/reporters/index.d.ts +1 -1
- package/dist/reporters/testops-reporter.d.ts +8 -161
- package/dist/reporters/testops-reporter.js +24 -537
- package/dist/state/state.js +1 -0
- package/dist/steps/step.d.ts +1 -1
- package/dist/steps/step.js +2 -2
- package/dist/utils/hostData.js +4 -2
- package/dist/utils/logger.js +17 -23
- package/dist/writer/fs-writer.d.ts +3 -3
- package/package.json +3 -2
- package/dist/utils/custom-boundary.d.ts +0 -26
- package/dist/utils/custom-boundary.js +0 -30
package/README.md
CHANGED
|
@@ -31,31 +31,31 @@ Qase JS Reporters can be configured in multiple ways:
|
|
|
31
31
|
|
|
32
32
|
All configuration options are listed in the table below:
|
|
33
33
|
|
|
34
|
-
| Description
|
|
35
|
-
|
|
36
|
-
| **Common**
|
|
37
|
-
| Mode of reporter
|
|
38
|
-
| Fallback mode of reporter
|
|
39
|
-
| Environment
|
|
40
|
-
| Root suite
|
|
41
|
-
| Enable debug logs
|
|
42
|
-
| Enable capture logs from `stdout` and `stderr`
|
|
43
|
-
| **Qase Report configuration**
|
|
44
|
-
| Driver used for report mode
|
|
45
|
-
| Path to save the report
|
|
46
|
-
| Local report format
|
|
47
|
-
| **Qase TestOps configuration**
|
|
48
|
-
| Token for [API access](https://developers.qase.io/#authentication)
|
|
49
|
-
| Qase API host. For enterprise users, specify
|
|
50
|
-
| Qase enterprise environment
|
|
34
|
+
| Description | Config file | Environment variable | Default value | Required | Possible values |
|
|
35
|
+
|-----------------------------------------------------------------------------------------------------------------------|----------------------------|---------------------------------|-----------------------------------------|----------|----------------------------|
|
|
36
|
+
| **Common** | | | | | |
|
|
37
|
+
| Mode of reporter | `mode` | `QASE_MODE` | `off` | No | `testops`, `report`, `off` |
|
|
38
|
+
| Fallback mode of reporter | `fallback` | `QASE_FALLBACK` | `off` | No | `testops`, `report`, `off` |
|
|
39
|
+
| Environment | `environment` | `QASE_ENVIRONMENT` | undefined | No | Any string |
|
|
40
|
+
| Root suite | `rootSuite` | `QASE_ROOT_SUITE` | undefined | No | Any string |
|
|
41
|
+
| Enable debug logs | `debug` | `QASE_DEBUG` | `False` | No | `True`, `False` |
|
|
42
|
+
| Enable capture logs from `stdout` and `stderr` | `testops.defect` | `QASE_CAPTURE_LOGS` | `False` | No | `True`, `False` |
|
|
43
|
+
| **Qase Report configuration** | | | | | |
|
|
44
|
+
| Driver used for report mode | `report.driver` | `QASE_REPORT_DRIVER` | `local` | No | `local` |
|
|
45
|
+
| Path to save the report | `report.connection.path` | `QASE_REPORT_CONNECTION_PATH` | `./build/qase-report` | | |
|
|
46
|
+
| Local report format | `report.connection.format` | `QASE_REPORT_CONNECTION_FORMAT` | `json` | | `json`, `jsonp` |
|
|
47
|
+
| **Qase TestOps configuration** | | | | | |
|
|
48
|
+
| Token for [API access](https://developers.qase.io/#authentication) | `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | undefined | Yes | Any string |
|
|
49
|
+
| Qase API host. For enterprise users, specify address: `example.qase.io` | `testops.api.host` | `QASE_TESTOPS_API_HOST` | `qase.io` | No | Any string |
|
|
50
|
+
| Qase enterprise environment | `testops.api.enterprise` | `QASE_TESTOPS_API_ENTERPRISE` | `False` | No | `True`, `False` |
|
|
51
51
|
| Code of your project, which you can take from the URL: `https://app.qase.io/project/DEMOTR` - `DEMOTR` is the project code | `testops.project` | `QASE_TESTOPS_PROJECT` | undefined | Yes | Any string |
|
|
52
|
-
| Qase test run ID
|
|
53
|
-
| Qase test run title
|
|
54
|
-
| Qase test run description
|
|
55
|
-
| Qase test run complete
|
|
56
|
-
| Qase test plan ID
|
|
57
|
-
| Size of batch for sending test results
|
|
58
|
-
| Enable defects for failed test cases
|
|
52
|
+
| Qase test run ID | `testops.run.id` | `QASE_TESTOPS_RUN_ID` | undefined | No | Any integer |
|
|
53
|
+
| Qase test run title | `testops.run.title` | `QASE_TESTOPS_RUN_TITLE` | `Automated run <Current date and time>` | No | Any string |
|
|
54
|
+
| Qase test run description | `testops.run.description` | `QASE_TESTOPS_RUN_DESCRIPTION` | `<Framework name> automated run` | No | Any string |
|
|
55
|
+
| Qase test run complete | `testops.run.complete` | `QASE_TESTOPS_RUN_COMPLETE` | `True` | | `True`, `False` |
|
|
56
|
+
| Qase test plan ID | `testops.plan.id` | `QASE_TESTOPS_PLAN_ID` | undefined | No | Any integer |
|
|
57
|
+
| Size of batch for sending test results | `testops.batch.size` | `QASE_TESTOPS_BATCH_SIZE` | `200` | No | Any integer |
|
|
58
|
+
| Enable defects for failed test cases | `testops.defect` | `QASE_TESTOPS_DEFECT` | `False` | No | `True`, `False` |
|
|
59
59
|
|
|
60
60
|
### Example `qase.config.json` config:
|
|
61
61
|
|
package/changelog.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# qase-javascript-commons@2.3.0
|
|
2
|
+
|
|
3
|
+
## What's new
|
|
4
|
+
|
|
5
|
+
Migrated to the new API clients for v1 (`qase-api-client`) and v2 (`qase-api-v2-client`) from the `qaseio`.
|
|
6
|
+
|
|
1
7
|
# qase-javascript-commons@2.2.18
|
|
2
8
|
|
|
3
9
|
## What's new
|
|
@@ -289,10 +295,10 @@ log a warning message.
|
|
|
289
295
|
|
|
290
296
|
## What's new
|
|
291
297
|
|
|
292
|
-
|
|
298
|
+
- The `useV2` option in the reporter's configuration will now enable using the experimental v2 API.
|
|
293
299
|
Before this fix, v1 API was used despite the configuration.
|
|
294
300
|
|
|
295
|
-
|
|
301
|
+
- Attachments from test steps will now be uploaded to Qase.
|
|
296
302
|
Before this fix, the reporter uploaded only the attachments made outside of any step scope.
|
|
297
303
|
|
|
298
304
|
# qase-javascript-commons@2.0.0-beta.10
|
|
@@ -380,10 +386,10 @@ where `10` is the size of the chunk in test result's count.
|
|
|
380
386
|
Qase TestOps API has two endpoints for reporting test results:
|
|
381
387
|
|
|
382
388
|
- Version 1, stable and used my most test reporters.
|
|
383
|
-
https://developers.qase.io/reference/create-result-bulk
|
|
389
|
+
<https://developers.qase.io/reference/create-result-bulk>
|
|
384
390
|
- Version 2, currently in beta access, and currently supported only
|
|
385
391
|
in the `playwright-qase-reporter`.
|
|
386
|
-
https://developers.qase.io/v2.0/reference/create-results-v2
|
|
392
|
+
<https://developers.qase.io/v2.0/reference/create-results-v2>
|
|
387
393
|
|
|
388
394
|
This commit introduces a way to select the API version to use.
|
|
389
395
|
It enables using all new features of v2 JS reporters with the stable v1 API,
|
|
@@ -398,7 +404,7 @@ To enable using API v2, set an environment variable before running the tests:
|
|
|
398
404
|
QASE_TESTOPS_API_V2=true
|
|
399
405
|
```
|
|
400
406
|
|
|
401
|
-
### Support adding test suite description to a test report
|
|
407
|
+
### Support adding test suite description to a test report
|
|
402
408
|
|
|
403
409
|
Test reporters can now test suite description to test results.
|
|
404
410
|
Such description can be collected from test's location and attributes
|
|
@@ -414,22 +420,22 @@ Add new data models:
|
|
|
414
420
|
|
|
415
421
|
## What's new
|
|
416
422
|
|
|
417
|
-
|
|
423
|
+
- Update the config of reporters. Added `captureLogs` field. If it is set to `true`, the reporter will capture logs from
|
|
418
424
|
the test framework.
|
|
419
|
-
|
|
425
|
+
- Added `getMimeType` function to the commons package. It returns the MIME type of the file by its extension.
|
|
420
426
|
|
|
421
427
|
# qase-javascript-commons@2.0.0-beta.4
|
|
422
428
|
|
|
423
429
|
## What's new
|
|
424
430
|
|
|
425
|
-
|
|
426
|
-
|
|
431
|
+
- Added support for uploading attachments from strings and buffers in the testops reporter.
|
|
432
|
+
- Changed data type of `content` in the attachment data from `any` to `string | Buffer`.
|
|
427
433
|
|
|
428
434
|
# qase-javascript-commons@2.0.0-beta.3
|
|
429
435
|
|
|
430
436
|
## What's new
|
|
431
437
|
|
|
432
|
-
|
|
438
|
+
- Changed data type of `fields` and `parameters` in the test result data
|
|
433
439
|
from `Map<string, string>` to `Record<string, string>`.
|
|
434
440
|
|
|
435
441
|
# qase-javascript-commons@2.0.0-beta.2
|
|
@@ -448,10 +454,10 @@ npm install playwright-qase-reporter@beta
|
|
|
448
454
|
|
|
449
455
|
## What's new
|
|
450
456
|
|
|
451
|
-
|
|
457
|
+
- Set a fallback reporter when the primary reporter can't be used,
|
|
452
458
|
such as when the `testops` reporter can't authenticate with the Qase API.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
459
|
+
- Rename some environment variables to keep naming consistent between reporters in all languages.
|
|
460
|
+
- Add several environment variables for new config options.
|
|
461
|
+
- Write outputs in JSONP format, which can be used with
|
|
456
462
|
[Qase Report](https://github.com/qase-tms/qase-report).
|
|
457
|
-
|
|
463
|
+
- Logic for handling test with multiple case IDs moved to the commons package.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Attachment, TestResultType } from '../models';
|
|
2
|
+
import { TestOpsOptionsType } from '../models/config/TestOpsOptionsType';
|
|
3
|
+
import { QaseError } from '../utils/qase-error';
|
|
4
|
+
import { IClient } from './interface';
|
|
5
|
+
import { LoggerInterface } from '../utils/logger';
|
|
6
|
+
export declare class ClientV1 implements IClient {
|
|
7
|
+
protected readonly logger: LoggerInterface;
|
|
8
|
+
protected readonly config: TestOpsOptionsType;
|
|
9
|
+
private readonly environment;
|
|
10
|
+
private readonly appUrl;
|
|
11
|
+
private readonly runClient;
|
|
12
|
+
private readonly environmentClient;
|
|
13
|
+
private readonly attachmentClient;
|
|
14
|
+
constructor(logger: LoggerInterface, config: TestOpsOptionsType, environment: string | undefined);
|
|
15
|
+
private createApiConfig;
|
|
16
|
+
uploadResults(_runId: number, _results: TestResultType[]): Promise<void>;
|
|
17
|
+
createRun(): Promise<number>;
|
|
18
|
+
completeRun(runId: number): Promise<void>;
|
|
19
|
+
protected uploadAttachments(attachments: Attachment[]): Promise<string[]>;
|
|
20
|
+
private prepareAttachmentData;
|
|
21
|
+
private getEnvironmentId;
|
|
22
|
+
private prepareRunObject;
|
|
23
|
+
/**
|
|
24
|
+
* Process error and throw QaseError
|
|
25
|
+
* @param {Error | AxiosError} error
|
|
26
|
+
* @param {string} message
|
|
27
|
+
* @param {object} model
|
|
28
|
+
* @private
|
|
29
|
+
*/
|
|
30
|
+
protected processError(error: unknown, message: string, model?: object): QaseError;
|
|
31
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ClientV1 = void 0;
|
|
7
|
+
const qase_api_client_1 = require("qase-api-client");
|
|
8
|
+
const is_axios_error_1 = require("../utils/is-axios-error");
|
|
9
|
+
const qase_error_1 = require("../utils/qase-error");
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const fs_1 = require("fs");
|
|
12
|
+
const dateUtils_1 = require("./dateUtils");
|
|
13
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
14
|
+
const DEFAULT_API_HOST = 'qase.io';
|
|
15
|
+
const API_BASE_URL = 'https://api-';
|
|
16
|
+
const APP_BASE_URL = 'https://app-';
|
|
17
|
+
const API_VERSION = '/v1';
|
|
18
|
+
var ApiErrorCode;
|
|
19
|
+
(function (ApiErrorCode) {
|
|
20
|
+
ApiErrorCode[ApiErrorCode["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
|
|
21
|
+
ApiErrorCode[ApiErrorCode["FORBIDDEN"] = 403] = "FORBIDDEN";
|
|
22
|
+
ApiErrorCode[ApiErrorCode["NOT_FOUND"] = 404] = "NOT_FOUND";
|
|
23
|
+
ApiErrorCode[ApiErrorCode["BAD_REQUEST"] = 400] = "BAD_REQUEST";
|
|
24
|
+
ApiErrorCode[ApiErrorCode["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
|
|
25
|
+
})(ApiErrorCode || (ApiErrorCode = {}));
|
|
26
|
+
class ClientV1 {
|
|
27
|
+
logger;
|
|
28
|
+
config;
|
|
29
|
+
environment;
|
|
30
|
+
appUrl;
|
|
31
|
+
runClient;
|
|
32
|
+
environmentClient;
|
|
33
|
+
attachmentClient;
|
|
34
|
+
constructor(logger, config, environment) {
|
|
35
|
+
this.logger = logger;
|
|
36
|
+
this.config = config;
|
|
37
|
+
this.environment = environment;
|
|
38
|
+
const { apiConfig, appUrl } = this.createApiConfig();
|
|
39
|
+
this.appUrl = appUrl;
|
|
40
|
+
this.runClient = new qase_api_client_1.RunsApi(apiConfig);
|
|
41
|
+
this.environmentClient = new qase_api_client_1.EnvironmentsApi(apiConfig);
|
|
42
|
+
this.attachmentClient = new qase_api_client_1.AttachmentsApi(apiConfig);
|
|
43
|
+
}
|
|
44
|
+
createApiConfig() {
|
|
45
|
+
const apiConfig = new qase_api_client_1.Configuration({ apiKey: this.config.api.token, formDataCtor: form_data_1.default });
|
|
46
|
+
if (this.config.api.host && this.config.api.host != DEFAULT_API_HOST) {
|
|
47
|
+
apiConfig.basePath = `${API_BASE_URL}${this.config.api.host}${API_VERSION}`;
|
|
48
|
+
return { apiConfig, appUrl: `${APP_BASE_URL}${this.config.api.host}` };
|
|
49
|
+
}
|
|
50
|
+
apiConfig.basePath = `https://api.${DEFAULT_API_HOST}${API_VERSION}`;
|
|
51
|
+
return { apiConfig, appUrl: `https://app.${DEFAULT_API_HOST}` };
|
|
52
|
+
}
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
54
|
+
uploadResults(_runId, _results) {
|
|
55
|
+
throw new Error('Use ClientV2 to upload results');
|
|
56
|
+
}
|
|
57
|
+
async createRun() {
|
|
58
|
+
if (this.config.run.id) {
|
|
59
|
+
return this.config.run.id;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const environmentId = await this.getEnvironmentId();
|
|
63
|
+
const runObject = this.prepareRunObject(environmentId);
|
|
64
|
+
this.logger.logDebug(`Creating test run: ${JSON.stringify(runObject)}`);
|
|
65
|
+
const { data } = await this.runClient.createRun(this.config.project, runObject);
|
|
66
|
+
if (!data.result?.id) {
|
|
67
|
+
throw new qase_error_1.QaseError('Failed to create test run');
|
|
68
|
+
}
|
|
69
|
+
this.logger.logDebug(`Test run created: ${JSON.stringify(data)}`);
|
|
70
|
+
return data.result.id;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw this.processError(error, 'Error creating test run');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async completeRun(runId) {
|
|
77
|
+
if (!this.config.run.complete) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
await this.runClient.completeRun(this.config.project, runId);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
throw this.processError(error, 'Error on completing run');
|
|
85
|
+
}
|
|
86
|
+
if (this.appUrl) {
|
|
87
|
+
const runUrl = `${this.appUrl}/run/${this.config.project}/dashboard/${runId}`;
|
|
88
|
+
this.logger.log((0, chalk_1.default) `{blue Test run link: ${runUrl}}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async uploadAttachments(attachments) {
|
|
92
|
+
if (!this.config.uploadAttachments) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const uploadedHashes = [];
|
|
96
|
+
for (const attachment of attachments) {
|
|
97
|
+
try {
|
|
98
|
+
this.logger.logDebug(`Uploading attachment: ${attachment.file_path ?? attachment.file_name}`);
|
|
99
|
+
const data = this.prepareAttachmentData(attachment);
|
|
100
|
+
const response = await this.attachmentClient.uploadAttachment(this.config.project, [data]);
|
|
101
|
+
const hash = response.data.result?.[0]?.hash;
|
|
102
|
+
if (hash) {
|
|
103
|
+
uploadedHashes.push(hash);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
this.logger.logError('Cannot upload attachment:', error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return uploadedHashes;
|
|
111
|
+
}
|
|
112
|
+
prepareAttachmentData(attachment) {
|
|
113
|
+
if (attachment.file_path) {
|
|
114
|
+
return {
|
|
115
|
+
name: attachment.file_name,
|
|
116
|
+
value: (0, fs_1.createReadStream)(attachment.file_path),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
name: attachment.file_name,
|
|
121
|
+
value: typeof attachment.content === 'string'
|
|
122
|
+
? Buffer.from(attachment.content)
|
|
123
|
+
: attachment.content,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async getEnvironmentId() {
|
|
127
|
+
if (!this.environment)
|
|
128
|
+
return undefined;
|
|
129
|
+
const { data } = await this.environmentClient.getEnvironments(this.config.project, undefined, this.environment, 100);
|
|
130
|
+
return data.result?.entities?.find((env) => env.slug === this.environment)?.id;
|
|
131
|
+
}
|
|
132
|
+
prepareRunObject(environmentId) {
|
|
133
|
+
const runObject = {
|
|
134
|
+
title: this.config.run.title ?? `Automated run ${new Date().toISOString()}`,
|
|
135
|
+
description: this.config.run.description ?? '',
|
|
136
|
+
is_autotest: true,
|
|
137
|
+
cases: [],
|
|
138
|
+
start_time: (0, dateUtils_1.getStartTime)(),
|
|
139
|
+
};
|
|
140
|
+
if (environmentId !== undefined) {
|
|
141
|
+
runObject.environment_id = environmentId;
|
|
142
|
+
}
|
|
143
|
+
if (this.config.plan.id) {
|
|
144
|
+
runObject.plan_id = this.config.plan.id;
|
|
145
|
+
}
|
|
146
|
+
return runObject;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Process error and throw QaseError
|
|
150
|
+
* @param {Error | AxiosError} error
|
|
151
|
+
* @param {string} message
|
|
152
|
+
* @param {object} model
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
processError(error, message, model) {
|
|
156
|
+
if (!(0, is_axios_error_1.isAxiosError)(error)) {
|
|
157
|
+
return new qase_error_1.QaseError(message, { cause: error });
|
|
158
|
+
}
|
|
159
|
+
const err = error;
|
|
160
|
+
const errorData = err.response?.data;
|
|
161
|
+
const status = err.response?.status;
|
|
162
|
+
switch (status) {
|
|
163
|
+
case ApiErrorCode.UNAUTHORIZED:
|
|
164
|
+
return new qase_error_1.QaseError(`${message}: Unauthorized. Please check your API token.`);
|
|
165
|
+
case ApiErrorCode.FORBIDDEN:
|
|
166
|
+
return new qase_error_1.QaseError(`${message}: ${errorData?.errorMessage ?? 'Forbidden'}`);
|
|
167
|
+
case ApiErrorCode.NOT_FOUND:
|
|
168
|
+
return new qase_error_1.QaseError(`${message}: Not found.`);
|
|
169
|
+
case ApiErrorCode.BAD_REQUEST:
|
|
170
|
+
case ApiErrorCode.UNPROCESSABLE_ENTITY:
|
|
171
|
+
return new qase_error_1.QaseError(`${message}: Bad request\n${JSON.stringify(errorData)}\nBody: ${JSON.stringify(model)}`);
|
|
172
|
+
default:
|
|
173
|
+
return new qase_error_1.QaseError(message, { cause: err });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
exports.ClientV1 = ClientV1;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ResultStepStatus } from "qase-api-v2-client";
|
|
2
|
+
import { StepStatusEnum, TestResultType, TestStatusEnum } from "../models";
|
|
3
|
+
import { LoggerInterface } from "../utils/logger";
|
|
4
|
+
import { ClientV1 } from "./clientV1";
|
|
5
|
+
import { TestOpsOptionsType } from "../models/config/TestOpsOptionsType";
|
|
6
|
+
export declare class ClientV2 extends ClientV1 {
|
|
7
|
+
private readonly rootSuite;
|
|
8
|
+
static statusMap: Record<TestStatusEnum, string>;
|
|
9
|
+
static stepStatusMap: Record<StepStatusEnum, ResultStepStatus>;
|
|
10
|
+
private readonly resultsClient;
|
|
11
|
+
constructor(logger: LoggerInterface, config: TestOpsOptionsType, environment: string | undefined, rootSuite: string | undefined);
|
|
12
|
+
private createApiConfigV2;
|
|
13
|
+
uploadResults(runId: number, results: TestResultType[]): Promise<void>;
|
|
14
|
+
private transformTestResult;
|
|
15
|
+
private transformParams;
|
|
16
|
+
private transformGroupParams;
|
|
17
|
+
private transformSteps;
|
|
18
|
+
private transformStep;
|
|
19
|
+
private createBaseResultStep;
|
|
20
|
+
private processTextStep;
|
|
21
|
+
private processGherkinStep;
|
|
22
|
+
private getExecution;
|
|
23
|
+
private getRelation;
|
|
24
|
+
private getDefaultSuiteRelation;
|
|
25
|
+
private buildSuiteData;
|
|
26
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ClientV2 = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const qase_api_v2_client_1 = require("qase-api-v2-client");
|
|
9
|
+
const models_1 = require("../models");
|
|
10
|
+
const clientV1_1 = require("./clientV1");
|
|
11
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
12
|
+
const API_CONFIG = {
|
|
13
|
+
DEFAULT_HOST: 'qase.io',
|
|
14
|
+
BASE_URL: 'https://api-',
|
|
15
|
+
VERSION: '/v2'
|
|
16
|
+
};
|
|
17
|
+
class ClientV2 extends clientV1_1.ClientV1 {
|
|
18
|
+
rootSuite;
|
|
19
|
+
static statusMap = {
|
|
20
|
+
[models_1.TestStatusEnum.passed]: 'passed',
|
|
21
|
+
[models_1.TestStatusEnum.failed]: 'failed',
|
|
22
|
+
[models_1.TestStatusEnum.skipped]: 'skipped',
|
|
23
|
+
[models_1.TestStatusEnum.disabled]: 'disabled',
|
|
24
|
+
[models_1.TestStatusEnum.blocked]: 'blocked',
|
|
25
|
+
[models_1.TestStatusEnum.invalid]: 'invalid',
|
|
26
|
+
};
|
|
27
|
+
static stepStatusMap = {
|
|
28
|
+
[models_1.StepStatusEnum.passed]: qase_api_v2_client_1.ResultStepStatus.PASSED,
|
|
29
|
+
[models_1.StepStatusEnum.failed]: qase_api_v2_client_1.ResultStepStatus.FAILED,
|
|
30
|
+
[models_1.StepStatusEnum.blocked]: qase_api_v2_client_1.ResultStepStatus.BLOCKED,
|
|
31
|
+
[models_1.StepStatusEnum.skipped]: qase_api_v2_client_1.ResultStepStatus.SKIPPED,
|
|
32
|
+
};
|
|
33
|
+
resultsClient;
|
|
34
|
+
constructor(logger, config, environment, rootSuite) {
|
|
35
|
+
super(logger, config, environment);
|
|
36
|
+
this.rootSuite = rootSuite;
|
|
37
|
+
const apiConfig = this.createApiConfigV2();
|
|
38
|
+
this.resultsClient = new qase_api_v2_client_1.ResultsApi(apiConfig);
|
|
39
|
+
}
|
|
40
|
+
createApiConfigV2() {
|
|
41
|
+
const apiConfig = new qase_api_v2_client_1.Configuration({ apiKey: this.config.api.token, formDataCtor: form_data_1.default });
|
|
42
|
+
apiConfig.basePath = this.config.api.host && this.config.api.host != API_CONFIG.DEFAULT_HOST
|
|
43
|
+
? `${API_CONFIG.BASE_URL}${this.config.api.host}${API_CONFIG.VERSION}`
|
|
44
|
+
: `https://api.${API_CONFIG.DEFAULT_HOST}${API_CONFIG.VERSION}`;
|
|
45
|
+
return apiConfig;
|
|
46
|
+
}
|
|
47
|
+
async uploadResults(runId, results) {
|
|
48
|
+
try {
|
|
49
|
+
const models = await Promise.all(results.map(result => this.transformTestResult(result)));
|
|
50
|
+
await this.resultsClient.createResultsV2(this.config.project, runId, {
|
|
51
|
+
results: models,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw this.processError(error, 'Error on uploading results', results);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async transformTestResult(result) {
|
|
59
|
+
const attachments = await this.uploadAttachments(result.attachments);
|
|
60
|
+
const steps = await this.transformSteps(result.steps, result.title);
|
|
61
|
+
const params = this.transformParams(result.params);
|
|
62
|
+
const groupParams = this.transformGroupParams(result.group_params, params);
|
|
63
|
+
const relations = this.getRelation(result.relations);
|
|
64
|
+
const model = {
|
|
65
|
+
title: result.title,
|
|
66
|
+
execution: this.getExecution(result.execution),
|
|
67
|
+
testops_ids: Array.isArray(result.testops_id)
|
|
68
|
+
? result.testops_id
|
|
69
|
+
: result.testops_id !== null ? [result.testops_id] : null,
|
|
70
|
+
attachments: attachments,
|
|
71
|
+
steps: steps,
|
|
72
|
+
params: params,
|
|
73
|
+
param_groups: groupParams,
|
|
74
|
+
relations: relations,
|
|
75
|
+
message: result.message,
|
|
76
|
+
fields: result.fields,
|
|
77
|
+
defect: this.config.defect ?? false,
|
|
78
|
+
};
|
|
79
|
+
this.logger.logDebug(`Transformed result: ${JSON.stringify(model)}`);
|
|
80
|
+
return model;
|
|
81
|
+
}
|
|
82
|
+
transformParams(params) {
|
|
83
|
+
const transformedParams = {};
|
|
84
|
+
for (const [key, value] of Object.entries(params)) {
|
|
85
|
+
if (value) {
|
|
86
|
+
transformedParams[key] = value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return transformedParams;
|
|
90
|
+
}
|
|
91
|
+
transformGroupParams(groupParams, params) {
|
|
92
|
+
const keys = Object.keys(groupParams);
|
|
93
|
+
if (keys.length === 0) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
for (const [key, value] of Object.entries(groupParams)) {
|
|
97
|
+
if (value) {
|
|
98
|
+
params[key] = value;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return [keys];
|
|
102
|
+
}
|
|
103
|
+
async transformSteps(steps, testTitle) {
|
|
104
|
+
return Promise.all(steps.map(step => this.transformStep(step, testTitle)));
|
|
105
|
+
}
|
|
106
|
+
async transformStep(step, testTitle) {
|
|
107
|
+
const attachmentHashes = await this.uploadAttachments(step.attachments);
|
|
108
|
+
const resultStep = this.createBaseResultStep(attachmentHashes, step.execution.status);
|
|
109
|
+
if (step.step_type === models_1.StepType.TEXT) {
|
|
110
|
+
this.processTextStep(step, resultStep, testTitle);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.processGherkinStep(step, resultStep);
|
|
114
|
+
}
|
|
115
|
+
if (step.steps.length > 0) {
|
|
116
|
+
resultStep.steps = await this.transformSteps(step.steps, testTitle);
|
|
117
|
+
}
|
|
118
|
+
return resultStep;
|
|
119
|
+
}
|
|
120
|
+
createBaseResultStep(attachmentHashes, status) {
|
|
121
|
+
return {
|
|
122
|
+
data: {
|
|
123
|
+
action: '',
|
|
124
|
+
},
|
|
125
|
+
execution: {
|
|
126
|
+
status: ClientV2.stepStatusMap[status],
|
|
127
|
+
attachments: attachmentHashes,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
processTextStep(step, resultStep, testTitle) {
|
|
132
|
+
if (!('action' in step.data) || !resultStep.data) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const stepData = step.data;
|
|
136
|
+
resultStep.data.action = stepData.action || 'Unnamed step';
|
|
137
|
+
if (stepData.action === '') {
|
|
138
|
+
this.logger.log((0, chalk_1.default) `{magenta Test '${testTitle}' has empty action in step. The reporter will mark this step as unnamed step.}`);
|
|
139
|
+
}
|
|
140
|
+
if (stepData.expected_result != null) {
|
|
141
|
+
resultStep.data.expected_result = stepData.expected_result;
|
|
142
|
+
}
|
|
143
|
+
if (stepData.data != null) {
|
|
144
|
+
resultStep.data.input_data = stepData.data;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
processGherkinStep(step, resultStep) {
|
|
148
|
+
if (!('keyword' in step.data) || !resultStep.data) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const stepData = step.data;
|
|
152
|
+
resultStep.data.action = stepData.keyword;
|
|
153
|
+
}
|
|
154
|
+
getExecution(exec) {
|
|
155
|
+
return {
|
|
156
|
+
status: ClientV2.statusMap[exec.status],
|
|
157
|
+
start_time: exec.start_time,
|
|
158
|
+
end_time: exec.end_time,
|
|
159
|
+
duration: exec.duration,
|
|
160
|
+
stacktrace: exec.stacktrace,
|
|
161
|
+
thread: exec.thread,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
getRelation(relation) {
|
|
165
|
+
if (!relation?.suite) {
|
|
166
|
+
return this.getDefaultSuiteRelation();
|
|
167
|
+
}
|
|
168
|
+
const suiteData = this.buildSuiteData(relation.suite.data);
|
|
169
|
+
return { suite: { data: suiteData } };
|
|
170
|
+
}
|
|
171
|
+
getDefaultSuiteRelation() {
|
|
172
|
+
if (!this.rootSuite)
|
|
173
|
+
return {};
|
|
174
|
+
return {
|
|
175
|
+
suite: {
|
|
176
|
+
data: [{
|
|
177
|
+
public_id: null,
|
|
178
|
+
title: this.rootSuite,
|
|
179
|
+
}],
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
buildSuiteData(suiteData) {
|
|
184
|
+
const result = [];
|
|
185
|
+
if (this.rootSuite) {
|
|
186
|
+
result.push({
|
|
187
|
+
public_id: null,
|
|
188
|
+
title: this.rootSuite,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return result.concat(suiteData.map(data => ({
|
|
192
|
+
public_id: null,
|
|
193
|
+
title: data.title,
|
|
194
|
+
})));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
exports.ClientV2 = ClientV2;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getStartTime = exports.formatUTCDate = void 0;
|
|
4
|
+
// Utils
|
|
5
|
+
const pad = (num) => num.toString().padStart(2, '0');
|
|
6
|
+
function formatUTCDate(date) {
|
|
7
|
+
const year = date.getUTCFullYear();
|
|
8
|
+
const month = pad(date.getUTCMonth() + 1);
|
|
9
|
+
const day = pad(date.getUTCDate());
|
|
10
|
+
const hours = pad(date.getUTCHours());
|
|
11
|
+
const minutes = pad(date.getUTCMinutes());
|
|
12
|
+
const seconds = pad(date.getUTCSeconds());
|
|
13
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
14
|
+
}
|
|
15
|
+
exports.formatUTCDate = formatUTCDate;
|
|
16
|
+
function getStartTime() {
|
|
17
|
+
const date = new Date();
|
|
18
|
+
return formatUTCDate(new Date(date.getTime() - 10000));
|
|
19
|
+
}
|
|
20
|
+
exports.getStartTime = getStartTime;
|
|
@@ -51,24 +51,6 @@ exports.configValidationSchema = {
|
|
|
51
51
|
type: 'string',
|
|
52
52
|
nullable: true,
|
|
53
53
|
},
|
|
54
|
-
headers: {
|
|
55
|
-
type: 'object',
|
|
56
|
-
nullable: true,
|
|
57
|
-
additionalProperties: false,
|
|
58
|
-
patternProperties: {
|
|
59
|
-
'^.*$': {
|
|
60
|
-
type: 'string',
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
retries: {
|
|
65
|
-
type: 'number',
|
|
66
|
-
nullable: true,
|
|
67
|
-
},
|
|
68
|
-
retryDelay: {
|
|
69
|
-
type: 'number',
|
|
70
|
-
nullable: true,
|
|
71
|
-
},
|
|
72
54
|
},
|
|
73
55
|
},
|
|
74
56
|
project: {
|
|
@@ -125,10 +107,6 @@ exports.configValidationSchema = {
|
|
|
125
107
|
type: 'boolean',
|
|
126
108
|
nullable: true,
|
|
127
109
|
},
|
|
128
|
-
useV2: {
|
|
129
|
-
type: 'boolean',
|
|
130
|
-
nullable: true,
|
|
131
|
-
},
|
|
132
110
|
},
|
|
133
111
|
},
|
|
134
112
|
report: {
|