qase-javascript-commons 2.5.0 → 2.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
# qase-javascript-commons@2.5.2
|
|
2
|
+
|
|
3
|
+
## What's new
|
|
4
|
+
|
|
5
|
+
Aligned report output with the cross-language Qase Report specification:
|
|
6
|
+
|
|
7
|
+
- Report file renamed from `report.json` to `run.json`, title changed to `"Test run"`.
|
|
8
|
+
- Stats model: replaced `broken` field with `blocked` and `invalid`, fixed status enum mapping.
|
|
9
|
+
- Result serialization: `testops_id` serializes as `testops_ids` (array), `group_params` as `param_groups` (array of arrays).
|
|
10
|
+
- Step serialization: `data` field serializes as `input_data`, attachments moved to `execution.attachments`.
|
|
11
|
+
- Gherkin-type steps automatically convert to TEXT format during serialization (`keyword + name` → `action`).
|
|
12
|
+
- Attachment `size` and `content` fields excluded from serialized output.
|
|
13
|
+
|
|
14
|
+
All changes are serialization-only — internal model interfaces are unchanged, no breaking changes for framework reporters.
|
|
15
|
+
|
|
16
|
+
# qase-javascript-commons@2.5.1
|
|
17
|
+
|
|
18
|
+
## What's new
|
|
19
|
+
|
|
20
|
+
Fixed issue with incorrect test status determination.
|
|
21
|
+
|
|
1
22
|
# qase-javascript-commons@2.5.0
|
|
2
23
|
|
|
3
24
|
## What's new
|
package/dist/models/stats.d.ts
CHANGED
|
@@ -41,4 +41,38 @@ export declare class ReportReporter extends AbstractReporter {
|
|
|
41
41
|
* @returns {TestStepType[]}
|
|
42
42
|
*/
|
|
43
43
|
private copyStepAttachments;
|
|
44
|
+
/**
|
|
45
|
+
* Serialize a test result to spec-compliant JSON format.
|
|
46
|
+
* Transforms internal model fields to match the Qase Report specification.
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
private serializeResultForReport;
|
|
50
|
+
/**
|
|
51
|
+
* Transform group_params Record to param_groups array of arrays.
|
|
52
|
+
* Same logic as clientV2.ts transformGroupParams.
|
|
53
|
+
* @private
|
|
54
|
+
*/
|
|
55
|
+
private transformGroupParams;
|
|
56
|
+
/**
|
|
57
|
+
* Serialize attachment for report output (exclude size and content fields).
|
|
58
|
+
* @private
|
|
59
|
+
*/
|
|
60
|
+
private serializeAttachment;
|
|
61
|
+
/**
|
|
62
|
+
* Serialize steps recursively, transforming:
|
|
63
|
+
* - data.data -> data.input_data (STEP-01)
|
|
64
|
+
* - attachments -> execution.attachments (STEP-02)
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
private serializeSteps;
|
|
68
|
+
/**
|
|
69
|
+
* Serialize a single step to spec-compliant format.
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
private serializeStep;
|
|
73
|
+
/**
|
|
74
|
+
* Serialize step data, transforming data.data -> data.input_data for text steps.
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
private serializeStepData;
|
|
44
78
|
}
|
|
@@ -76,12 +76,14 @@ class ReportReporter extends abstract_reporter_1.AbstractReporter {
|
|
|
76
76
|
},
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
// Serialize to spec-compliant format before writing
|
|
80
|
+
const serialized = this.serializeResultForReport(result);
|
|
81
|
+
await this.writer.writeTestResult(serialized);
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
async complete() {
|
|
83
85
|
const report = {
|
|
84
|
-
title: 'Test
|
|
86
|
+
title: 'Test run',
|
|
85
87
|
execution: {
|
|
86
88
|
start_time: this.startTime,
|
|
87
89
|
end_time: Date.now(),
|
|
@@ -93,7 +95,8 @@ class ReportReporter extends abstract_reporter_1.AbstractReporter {
|
|
|
93
95
|
passed: 0,
|
|
94
96
|
failed: 0,
|
|
95
97
|
skipped: 0,
|
|
96
|
-
|
|
98
|
+
blocked: 0,
|
|
99
|
+
invalid: 0,
|
|
97
100
|
muted: 0,
|
|
98
101
|
},
|
|
99
102
|
results: [],
|
|
@@ -115,10 +118,10 @@ class ReportReporter extends abstract_reporter_1.AbstractReporter {
|
|
|
115
118
|
report.stats.skipped++;
|
|
116
119
|
break;
|
|
117
120
|
case models_1.TestStatusEnum.invalid:
|
|
118
|
-
report.stats.
|
|
121
|
+
report.stats.invalid++;
|
|
119
122
|
break;
|
|
120
123
|
case models_1.TestStatusEnum.blocked:
|
|
121
|
-
report.stats.
|
|
124
|
+
report.stats.blocked++;
|
|
122
125
|
break;
|
|
123
126
|
}
|
|
124
127
|
report.execution.cumulative_duration += result.execution.duration ?? 0;
|
|
@@ -152,5 +155,128 @@ class ReportReporter extends abstract_reporter_1.AbstractReporter {
|
|
|
152
155
|
}
|
|
153
156
|
return steps;
|
|
154
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Serialize a test result to spec-compliant JSON format.
|
|
160
|
+
* Transforms internal model fields to match the Qase Report specification.
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
serializeResultForReport(result) {
|
|
164
|
+
// Transform testops_id -> testops_ids (RSLT-01)
|
|
165
|
+
let testopsIds = null;
|
|
166
|
+
if (result.testops_id !== null) {
|
|
167
|
+
testopsIds = Array.isArray(result.testops_id)
|
|
168
|
+
? result.testops_id
|
|
169
|
+
: [result.testops_id];
|
|
170
|
+
}
|
|
171
|
+
// Transform group_params -> param_groups (RSLT-02)
|
|
172
|
+
const paramGroups = this.transformGroupParams(result.group_params);
|
|
173
|
+
// Serialize attachments (exclude size and content fields)
|
|
174
|
+
const attachments = result.attachments.map(att => this.serializeAttachment(att));
|
|
175
|
+
// Serialize steps (handle data.data -> data.input_data and attachments -> execution.attachments)
|
|
176
|
+
const steps = this.serializeSteps(result.steps);
|
|
177
|
+
// Build spec-compliant result object
|
|
178
|
+
const serialized = {
|
|
179
|
+
id: result.id,
|
|
180
|
+
title: result.title,
|
|
181
|
+
signature: result.signature,
|
|
182
|
+
execution: result.execution,
|
|
183
|
+
fields: result.fields,
|
|
184
|
+
attachments: attachments,
|
|
185
|
+
steps: steps,
|
|
186
|
+
params: result.params,
|
|
187
|
+
param_groups: paramGroups,
|
|
188
|
+
testops_ids: testopsIds,
|
|
189
|
+
relations: result.relations,
|
|
190
|
+
muted: result.muted,
|
|
191
|
+
message: result.message,
|
|
192
|
+
};
|
|
193
|
+
// Internal-only fields are excluded: testops_id, group_params, run_id, author, testops_project_mapping, preparedAttachments
|
|
194
|
+
return serialized;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Transform group_params Record to param_groups array of arrays.
|
|
198
|
+
* Same logic as clientV2.ts transformGroupParams.
|
|
199
|
+
* @private
|
|
200
|
+
*/
|
|
201
|
+
transformGroupParams(groupParams) {
|
|
202
|
+
const keys = Object.keys(groupParams);
|
|
203
|
+
if (keys.length === 0) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
return [keys];
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Serialize attachment for report output (exclude size and content fields).
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
serializeAttachment(att) {
|
|
213
|
+
return {
|
|
214
|
+
id: att.id,
|
|
215
|
+
file_name: att.file_name,
|
|
216
|
+
mime_type: att.mime_type,
|
|
217
|
+
file_path: att.file_path,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Serialize steps recursively, transforming:
|
|
222
|
+
* - data.data -> data.input_data (STEP-01)
|
|
223
|
+
* - attachments -> execution.attachments (STEP-02)
|
|
224
|
+
* @private
|
|
225
|
+
*/
|
|
226
|
+
serializeSteps(steps) {
|
|
227
|
+
return steps.map(step => this.serializeStep(step));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Serialize a single step to spec-compliant format.
|
|
231
|
+
* @private
|
|
232
|
+
*/
|
|
233
|
+
serializeStep(step) {
|
|
234
|
+
// Transform step data (handle data.data -> data.input_data for text steps)
|
|
235
|
+
const data = this.serializeStepData(step);
|
|
236
|
+
// Serialize step attachments
|
|
237
|
+
const attachments = step.attachments.map(att => this.serializeAttachment(att));
|
|
238
|
+
// Move attachments into execution.attachments (STEP-02)
|
|
239
|
+
const execution = {
|
|
240
|
+
...step.execution,
|
|
241
|
+
attachments: attachments,
|
|
242
|
+
};
|
|
243
|
+
// Recursively serialize nested steps
|
|
244
|
+
const nestedSteps = this.serializeSteps(step.steps);
|
|
245
|
+
return {
|
|
246
|
+
id: step.id,
|
|
247
|
+
step_type: step.step_type,
|
|
248
|
+
data: data,
|
|
249
|
+
parent_id: step.parent_id,
|
|
250
|
+
execution: execution,
|
|
251
|
+
steps: nestedSteps,
|
|
252
|
+
// Note: attachments field is NOT at top-level in serialized output
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Serialize step data, transforming data.data -> data.input_data for text steps.
|
|
257
|
+
* @private
|
|
258
|
+
*/
|
|
259
|
+
serializeStepData(step) {
|
|
260
|
+
const data = { ...step.data };
|
|
261
|
+
// For text steps, rename data.data to data.input_data (STEP-01)
|
|
262
|
+
if (step.step_type === models_1.StepType.TEXT && 'data' in data) {
|
|
263
|
+
const textData = data;
|
|
264
|
+
return {
|
|
265
|
+
action: textData.action,
|
|
266
|
+
expected_result: textData.expected_result,
|
|
267
|
+
input_data: textData.data, // Rename: data -> input_data
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// For gherkin steps, convert to text format (STEP-03)
|
|
271
|
+
if (step.step_type === models_1.StepType.GHERKIN && 'keyword' in data && 'name' in data) {
|
|
272
|
+
const gherkinData = data;
|
|
273
|
+
return {
|
|
274
|
+
action: `${gherkinData.keyword} ${gherkinData.name}`,
|
|
275
|
+
expected_result: null,
|
|
276
|
+
input_data: null, // JS GherkinData has no data field
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return data;
|
|
280
|
+
}
|
|
155
281
|
}
|
|
156
282
|
exports.ReportReporter = ReportReporter;
|
|
@@ -14,7 +14,7 @@ function determineTestStatus(error, originalStatus) {
|
|
|
14
14
|
return mapOriginalStatus(originalStatus);
|
|
15
15
|
}
|
|
16
16
|
// Check if it's an assertion error
|
|
17
|
-
if (isAssertionError(error)) {
|
|
17
|
+
if (isAssertionError(error, originalStatus)) {
|
|
18
18
|
return test_execution_1.TestStatusEnum.failed;
|
|
19
19
|
}
|
|
20
20
|
// For all other errors, return invalid
|
|
@@ -23,16 +23,19 @@ function determineTestStatus(error, originalStatus) {
|
|
|
23
23
|
/**
|
|
24
24
|
* Checks if error is an assertion error
|
|
25
25
|
* @param error - Error object
|
|
26
|
+
* @param originalStatus - Original test status from test runner
|
|
26
27
|
* @returns boolean - true if assertion error
|
|
27
28
|
*/
|
|
28
|
-
function isAssertionError(error) {
|
|
29
|
+
function isAssertionError(error, originalStatus) {
|
|
29
30
|
const errorMessage = error.message.toLowerCase();
|
|
30
31
|
const errorStack = error.stack?.toLowerCase() || '';
|
|
32
|
+
const normalizedOriginalStatus = originalStatus.toLowerCase();
|
|
31
33
|
// Common assertion error patterns
|
|
32
34
|
const assertionPatterns = [
|
|
33
35
|
'expect',
|
|
34
36
|
'assert',
|
|
35
37
|
'matcher',
|
|
38
|
+
'objectcontaining',
|
|
36
39
|
'assertion',
|
|
37
40
|
'expected',
|
|
38
41
|
'actual',
|
|
@@ -77,6 +80,14 @@ function isAssertionError(error) {
|
|
|
77
80
|
const nonAssertionPatternsWithoutTimeout = nonAssertionPatterns.filter(pattern => pattern !== 'timeout');
|
|
78
81
|
const hasNonAssertionPattern = nonAssertionPatternsWithoutTimeout.some(pattern => errorMessage.includes(pattern) || errorStack.includes(pattern));
|
|
79
82
|
if (hasNonAssertionPattern) {
|
|
83
|
+
const hasAssertionContext = assertionPatterns.some(pattern => errorMessage.includes(pattern) || errorStack.includes(pattern));
|
|
84
|
+
const isRunnerFailed = normalizedOriginalStatus === 'failed' || normalizedOriginalStatus === 'timedout' || normalizedOriginalStatus === 'interrupted';
|
|
85
|
+
const isSyntaxError = errorMessage.includes('syntaxerror') || errorStack.includes('syntaxerror');
|
|
86
|
+
// When runner reported failure and error has assertion context (expect, ObjectContaining, diff),
|
|
87
|
+
// treat as Failed. Exception: SyntaxError (e.g. "Unexpected token") contains "expected" as false positive.
|
|
88
|
+
if (isRunnerFailed && hasAssertionContext && !isSyntaxError) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
80
91
|
return false;
|
|
81
92
|
}
|
|
82
93
|
// For timeout errors without expect, treat as invalid
|
|
@@ -99,6 +110,7 @@ function isAssertionError(error) {
|
|
|
99
110
|
* @returns TestStatusEnum
|
|
100
111
|
*/
|
|
101
112
|
function mapOriginalStatus(originalStatus) {
|
|
113
|
+
// Keys must be lowercase to match normalizedStatus (case-insensitive matching)
|
|
102
114
|
const statusMap = {
|
|
103
115
|
'passed': test_execution_1.TestStatusEnum.passed,
|
|
104
116
|
'failed': test_execution_1.TestStatusEnum.failed,
|
|
@@ -107,10 +119,9 @@ function mapOriginalStatus(originalStatus) {
|
|
|
107
119
|
'pending': test_execution_1.TestStatusEnum.skipped,
|
|
108
120
|
'todo': test_execution_1.TestStatusEnum.disabled,
|
|
109
121
|
'focused': test_execution_1.TestStatusEnum.passed,
|
|
110
|
-
'
|
|
122
|
+
'timedout': test_execution_1.TestStatusEnum.failed,
|
|
111
123
|
'interrupted': test_execution_1.TestStatusEnum.failed,
|
|
112
124
|
};
|
|
113
|
-
// Convert to lowercase for case-insensitive matching
|
|
114
125
|
const normalizedStatus = originalStatus.toLowerCase();
|
|
115
126
|
return statusMap[normalizedStatus] || test_execution_1.TestStatusEnum.skipped;
|
|
116
127
|
}
|
package/dist/writer/fs-writer.js
CHANGED
|
@@ -103,7 +103,7 @@ class FsWriter {
|
|
|
103
103
|
}
|
|
104
104
|
catch (error) { /* ignore */
|
|
105
105
|
}
|
|
106
|
-
const filePath = path.join(this.path, `
|
|
106
|
+
const filePath = path.join(this.path, `run.${this.format}`);
|
|
107
107
|
(0, fs_1.writeFileSync)(filePath, await this.formatter.format(results));
|
|
108
108
|
return filePath;
|
|
109
109
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qase-javascript-commons",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"description": "Qase JS Reporters",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@types/minimatch": "^6.0.0",
|
|
49
49
|
"@types/node": "^20.19.25",
|
|
50
50
|
"@types/uuid": "^9.0.8",
|
|
51
|
-
"axios": "^1.13.
|
|
51
|
+
"axios": "^1.13.5",
|
|
52
52
|
"jest": "^29.7.0",
|
|
53
53
|
"ts-jest": "^29.4.5"
|
|
54
54
|
}
|