qase-javascript-commons 2.6.6 → 2.7.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/changelog.md +15 -0
- package/dist/internal/ids-parser.d.ts +7 -0
- package/dist/internal/ids-parser.js +18 -0
- package/dist/internal/index.d.ts +7 -0
- package/dist/internal/index.js +13 -0
- package/dist/internal/step-parser.d.ts +11 -0
- package/dist/internal/step-parser.js +28 -0
- package/dist/internal/suite-file.d.ts +13 -0
- package/dist/internal/suite-file.js +16 -0
- package/dist/internal/suite-normalizer.d.ts +6 -0
- package/dist/internal/suite-normalizer.js +11 -0
- package/dist/internal/title.d.ts +5 -0
- package/dist/internal/title.js +15 -0
- package/dist/profilers/http-interceptor.d.ts +7 -0
- package/dist/profilers/http-interceptor.js +9 -2
- package/dist/reporters/report-reporter.js +4 -3
- package/dist/steps/step.js +11 -13
- package/package.json +8 -3
package/changelog.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## 2.7.1
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
- HTTP/fetch profiler steps now emit `execution.start_time` and `execution.end_time` as Unix epoch seconds (with fractional ms), matching the Qase API contract. Previously the interceptor wrote `Date.now()` milliseconds directly into both fields, placing every captured request step ~55 years in the future on the timeline. `execution.duration` remains in milliseconds.
|
|
5
|
+
- `ReportReporter` test-run summary (`report.execution.start_time` / `end_time` in the generated JSON report) is now emitted in Unix seconds instead of milliseconds; this aligns the report-mode file with the v2 API spec consumed by `reporters-validator`.
|
|
6
|
+
- `QaseStep.run` (the shared user-facing `qase.step()` API used by Jest, Vitest, WebdriverIO and TestCafe) now emits `execution.start_time` / `end_time` in Unix seconds instead of raw `Date.now()` milliseconds, and populates `execution.duration` (ms). Previously every `qase.step()` call landed ~55 years in the future on the timeline and reported `null` duration.
|
|
7
|
+
|
|
8
|
+
## 2.7.0
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- New internal subpath `qase-javascript-commons/internal` exposing helpers for use by Qase reporters in this monorepo: `removeQaseIdsFromTitle`, `extractAndCleanStep`, `getFile`, `parseQaseIdsFromString`, `normalizeSuitePart`. The `/internal` subpath is intended for Qase-owned reporter packages and may change without a major version bump.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- `removeQaseIdsFromTitle` regex unified across reporters to `/\(Qase ID:? ([\d,]+)\)$/i`. This is more permissive than the previous mocha/wdio variants (case-insensitive; also accepts `(Qase ID 1)` without colon) and adds a trailing-position anchor (`$`).
|
|
15
|
+
|
|
1
16
|
# qase-javascript-commons@2.6.6
|
|
2
17
|
|
|
3
18
|
## Bug fixes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a comma-separated string of Qase IDs into an array of numbers.
|
|
3
|
+
* Whitespace around each entry is trimmed; non-numeric entries are skipped.
|
|
4
|
+
*
|
|
5
|
+
* Examples: "1,2,3" → [1, 2, 3], "42" → [42], "" → [].
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseQaseIdsFromString(value: string): number[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseQaseIdsFromString = parseQaseIdsFromString;
|
|
4
|
+
/**
|
|
5
|
+
* Parses a comma-separated string of Qase IDs into an array of numbers.
|
|
6
|
+
* Whitespace around each entry is trimmed; non-numeric entries are skipped.
|
|
7
|
+
*
|
|
8
|
+
* Examples: "1,2,3" → [1, 2, 3], "42" → [42], "" → [].
|
|
9
|
+
*/
|
|
10
|
+
function parseQaseIdsFromString(value) {
|
|
11
|
+
if (!value?.trim()) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return value
|
|
15
|
+
.split(',')
|
|
16
|
+
.map((part) => parseInt(part.trim(), 10))
|
|
17
|
+
.filter((n) => !Number.isNaN(n));
|
|
18
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { removeQaseIdsFromTitle } from './title';
|
|
2
|
+
export { extractAndCleanStep } from './step-parser';
|
|
3
|
+
export type { ExtractedStep } from './step-parser';
|
|
4
|
+
export { getFile } from './suite-file';
|
|
5
|
+
export type { FileSuiteNode } from './suite-file';
|
|
6
|
+
export { parseQaseIdsFromString } from './ids-parser';
|
|
7
|
+
export { normalizeSuitePart } from './suite-normalizer';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeSuitePart = exports.parseQaseIdsFromString = exports.getFile = exports.extractAndCleanStep = exports.removeQaseIdsFromTitle = void 0;
|
|
4
|
+
var title_1 = require("./title");
|
|
5
|
+
Object.defineProperty(exports, "removeQaseIdsFromTitle", { enumerable: true, get: function () { return title_1.removeQaseIdsFromTitle; } });
|
|
6
|
+
var step_parser_1 = require("./step-parser");
|
|
7
|
+
Object.defineProperty(exports, "extractAndCleanStep", { enumerable: true, get: function () { return step_parser_1.extractAndCleanStep; } });
|
|
8
|
+
var suite_file_1 = require("./suite-file");
|
|
9
|
+
Object.defineProperty(exports, "getFile", { enumerable: true, get: function () { return suite_file_1.getFile; } });
|
|
10
|
+
var ids_parser_1 = require("./ids-parser");
|
|
11
|
+
Object.defineProperty(exports, "parseQaseIdsFromString", { enumerable: true, get: function () { return ids_parser_1.parseQaseIdsFromString; } });
|
|
12
|
+
var suite_normalizer_1 = require("./suite-normalizer");
|
|
13
|
+
Object.defineProperty(exports, "normalizeSuitePart", { enumerable: true, get: function () { return suite_normalizer_1.normalizeSuitePart; } });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ExtractedStep {
|
|
2
|
+
expectedResult: string | null;
|
|
3
|
+
data: string | null;
|
|
4
|
+
cleanedString: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Parses a step string for inline `QaseExpRes:` (expected result) and `QaseData:`
|
|
8
|
+
* (data) markers. Returns the extracted parts and the input string with markers
|
|
9
|
+
* removed. If no markers are present, returns nulls and the original string.
|
|
10
|
+
*/
|
|
11
|
+
export declare function extractAndCleanStep(input: string): ExtractedStep;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractAndCleanStep = extractAndCleanStep;
|
|
4
|
+
/**
|
|
5
|
+
* Parses a step string for inline `QaseExpRes:` (expected result) and `QaseData:`
|
|
6
|
+
* (data) markers. Returns the extracted parts and the input string with markers
|
|
7
|
+
* removed. If no markers are present, returns nulls and the original string.
|
|
8
|
+
*/
|
|
9
|
+
function extractAndCleanStep(input) {
|
|
10
|
+
let expectedResult = null;
|
|
11
|
+
let data = null;
|
|
12
|
+
let cleanedString = input;
|
|
13
|
+
const hasExpectedResult = input.includes('QaseExpRes:');
|
|
14
|
+
const hasData = input.includes('QaseData:');
|
|
15
|
+
if (hasExpectedResult || hasData) {
|
|
16
|
+
const regex = /QaseExpRes:\s*:?\s*(.*?)\s*(?=QaseData:|$)QaseData:\s*:?\s*(.*)?/;
|
|
17
|
+
const match = input.match(regex);
|
|
18
|
+
if (match) {
|
|
19
|
+
expectedResult = match[1]?.trim() ?? null;
|
|
20
|
+
data = match[2]?.trim() ?? null;
|
|
21
|
+
cleanedString = input
|
|
22
|
+
.replace(/QaseExpRes:\s*:?\s*.*?(?=QaseData:|$)/, '')
|
|
23
|
+
.replace(/QaseData:\s*:?\s*.*/, '')
|
|
24
|
+
.trim();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { expectedResult, data, cleanedString };
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural shape compatible with both Mocha's `Suite` (used by
|
|
3
|
+
* qase-mocha) and Cypress's runner Suite (used by qase-cypress).
|
|
4
|
+
*/
|
|
5
|
+
export interface FileSuiteNode {
|
|
6
|
+
file?: string;
|
|
7
|
+
parent?: FileSuiteNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Walks up `node.parent` until a node with a non-empty `file` property is
|
|
11
|
+
* reached. Returns the file path, or undefined if no ancestor has one.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getFile(node: FileSuiteNode): string | undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getFile = getFile;
|
|
4
|
+
/**
|
|
5
|
+
* Walks up `node.parent` until a node with a non-empty `file` property is
|
|
6
|
+
* reached. Returns the file path, or undefined if no ancestor has one.
|
|
7
|
+
*/
|
|
8
|
+
function getFile(node) {
|
|
9
|
+
if (node.file) {
|
|
10
|
+
return node.file;
|
|
11
|
+
}
|
|
12
|
+
if (node.parent) {
|
|
13
|
+
return getFile(node.parent);
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeSuitePart = normalizeSuitePart;
|
|
4
|
+
/**
|
|
5
|
+
* Lowercases the input and replaces every whitespace character with an
|
|
6
|
+
* underscore. Used by reporters to normalize suite/title segments before
|
|
7
|
+
* passing them into `generateSignature`.
|
|
8
|
+
*/
|
|
9
|
+
function normalizeSuitePart(value) {
|
|
10
|
+
return value.toLowerCase().replace(/\s/g, '_');
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removeQaseIdsFromTitle = removeQaseIdsFromTitle;
|
|
4
|
+
const QASE_ID_TRAILER_REGEXP = /\(Qase ID:? ([\d,]+)\)$/i;
|
|
5
|
+
/**
|
|
6
|
+
* Strips a trailing `(Qase ID: 1,2,3)` (or `(Qase ID 1)` without colon) from a test title.
|
|
7
|
+
* Returns the original title untouched if no match is found.
|
|
8
|
+
*/
|
|
9
|
+
function removeQaseIdsFromTitle(title) {
|
|
10
|
+
const match = title.match(QASE_ID_TRAILER_REGEXP);
|
|
11
|
+
if (match) {
|
|
12
|
+
return title.replace(match[0], '').trimEnd();
|
|
13
|
+
}
|
|
14
|
+
return title;
|
|
15
|
+
}
|
|
@@ -28,6 +28,13 @@ export declare function extractRequestInfo(args: [
|
|
|
28
28
|
* to `Record<string, string>`. Multi-value headers joined with `', '`. Undefined values skipped.
|
|
29
29
|
*/
|
|
30
30
|
export declare function headersToRecord(headers: http.IncomingHttpHeaders): Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Builds a REQUEST-type step from raw timing data.
|
|
33
|
+
*
|
|
34
|
+
* `startTime` / `endTime` are expected as `Date.now()` values (ms since epoch).
|
|
35
|
+
* They are normalized to Unix seconds (with fractional ms) for `step.execution.start_time` /
|
|
36
|
+
* `end_time` per Qase API spec; `duration` stays in ms.
|
|
37
|
+
*/
|
|
31
38
|
export declare function buildRequestStep(params: {
|
|
32
39
|
method: string;
|
|
33
40
|
url: string;
|
|
@@ -68,6 +68,13 @@ function headersToRecord(headers) {
|
|
|
68
68
|
return result;
|
|
69
69
|
}
|
|
70
70
|
// ─── Utility: buildRequestStep ───────────────────────────────────────────────
|
|
71
|
+
/**
|
|
72
|
+
* Builds a REQUEST-type step from raw timing data.
|
|
73
|
+
*
|
|
74
|
+
* `startTime` / `endTime` are expected as `Date.now()` values (ms since epoch).
|
|
75
|
+
* They are normalized to Unix seconds (with fractional ms) for `step.execution.start_time` /
|
|
76
|
+
* `end_time` per Qase API spec; `duration` stays in ms.
|
|
77
|
+
*/
|
|
71
78
|
function buildRequestStep(params) {
|
|
72
79
|
const step = new test_step_1.TestStepType(test_step_1.StepType.REQUEST);
|
|
73
80
|
step.id = (0, uuid_1.v4)();
|
|
@@ -79,8 +86,8 @@ function buildRequestStep(params) {
|
|
|
79
86
|
data.status_code = params.statusCode;
|
|
80
87
|
data.response_body = params.responseBody;
|
|
81
88
|
data.response_headers = params.responseHeaders;
|
|
82
|
-
step.execution.start_time = params.startTime;
|
|
83
|
-
step.execution.end_time = params.endTime;
|
|
89
|
+
step.execution.start_time = params.startTime / 1000;
|
|
90
|
+
step.execution.end_time = params.endTime / 1000;
|
|
84
91
|
step.execution.duration = params.endTime - params.startTime;
|
|
85
92
|
step.execution.status = params.statusCode >= 400 ? step_execution_1.StepStatusEnum.failed : step_execution_1.StepStatusEnum.passed;
|
|
86
93
|
return step;
|
|
@@ -89,12 +89,13 @@ class ReportReporter extends abstract_reporter_1.AbstractReporter {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
async complete() {
|
|
92
|
+
const endTime = Date.now();
|
|
92
93
|
const report = {
|
|
93
94
|
title: 'Test run',
|
|
94
95
|
execution: {
|
|
95
|
-
start_time: this.startTime,
|
|
96
|
-
end_time:
|
|
97
|
-
duration:
|
|
96
|
+
start_time: this.startTime / 1000,
|
|
97
|
+
end_time: endTime / 1000,
|
|
98
|
+
duration: endTime - this.startTime,
|
|
98
99
|
cumulative_duration: 0,
|
|
99
100
|
},
|
|
100
101
|
stats: {
|
package/dist/steps/step.js
CHANGED
|
@@ -52,32 +52,30 @@ class QaseStep {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
async run(body, messageEmitter) {
|
|
55
|
-
const
|
|
55
|
+
const startMs = Date.now();
|
|
56
56
|
const step = new models_1.TestStepType();
|
|
57
57
|
step.data = {
|
|
58
58
|
action: this.name,
|
|
59
59
|
expected_result: null,
|
|
60
60
|
data: null,
|
|
61
61
|
};
|
|
62
|
+
// Per Qase API spec: start_time / end_time are Unix epoch seconds (with
|
|
63
|
+
// fractional ms); duration is milliseconds.
|
|
64
|
+
const buildExecution = (status, endMs) => ({
|
|
65
|
+
start_time: startMs / 1000,
|
|
66
|
+
end_time: endMs / 1000,
|
|
67
|
+
status,
|
|
68
|
+
duration: endMs - startMs,
|
|
69
|
+
});
|
|
62
70
|
try {
|
|
63
71
|
await body.call(this, this);
|
|
64
|
-
step.execution =
|
|
65
|
-
start_time: startDate,
|
|
66
|
-
end_time: new Date().getTime(),
|
|
67
|
-
status: models_1.StepStatusEnum.passed,
|
|
68
|
-
duration: null,
|
|
69
|
-
};
|
|
72
|
+
step.execution = buildExecution(models_1.StepStatusEnum.passed, Date.now());
|
|
70
73
|
step.attachments = this.attachments;
|
|
71
74
|
step.steps = this.steps;
|
|
72
75
|
await messageEmitter(step);
|
|
73
76
|
}
|
|
74
77
|
catch (error) {
|
|
75
|
-
step.execution =
|
|
76
|
-
start_time: startDate,
|
|
77
|
-
end_time: new Date().getTime(),
|
|
78
|
-
status: models_1.StepStatusEnum.failed,
|
|
79
|
-
duration: null,
|
|
80
|
-
};
|
|
78
|
+
step.execution = buildExecution(models_1.StepStatusEnum.failed, Date.now());
|
|
81
79
|
step.attachments = this.attachments;
|
|
82
80
|
step.steps = this.steps;
|
|
83
81
|
await messageEmitter(step);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qase-javascript-commons",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "Qase JS Reporters",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -13,11 +13,16 @@
|
|
|
13
13
|
"types": "./dist/profilers/index.d.ts",
|
|
14
14
|
"default": "./dist/profilers/index.js"
|
|
15
15
|
},
|
|
16
|
+
"./internal": {
|
|
17
|
+
"types": "./dist/internal/index.d.ts",
|
|
18
|
+
"default": "./dist/internal/index.js"
|
|
19
|
+
},
|
|
16
20
|
"./package.json": "./package.json"
|
|
17
21
|
},
|
|
18
22
|
"typesVersions": {
|
|
19
23
|
"*": {
|
|
20
|
-
"profilers": ["./dist/profilers/index.d.ts"]
|
|
24
|
+
"profilers": ["./dist/profilers/index.d.ts"],
|
|
25
|
+
"internal": ["./dist/internal/index.d.ts"]
|
|
21
26
|
}
|
|
22
27
|
},
|
|
23
28
|
"homepage": "https://github.com/qase-tms/qase-javascript",
|
|
@@ -63,7 +68,7 @@
|
|
|
63
68
|
"@types/mime-types": "^2.1.4",
|
|
64
69
|
"@types/minimatch": "^6.0.0",
|
|
65
70
|
"@types/uuid": "^9.0.8",
|
|
66
|
-
"axios": "^1.15.
|
|
71
|
+
"axios": "^1.15.2",
|
|
67
72
|
"jest": "^29.7.0",
|
|
68
73
|
"ts-jest": "^29.4.5"
|
|
69
74
|
}
|