@testomatio/reporter 2.3.8 → 2.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/adapter/codecept.js +12 -9
- package/lib/client.d.ts +0 -10
- package/lib/client.js +14 -126
- package/lib/junit-adapter/csharp.js +4 -1
- package/lib/junit-adapter/nunit-parser.js +4 -2
- package/lib/pipe/bitbucket.js +5 -5
- package/lib/pipe/gitlab.js +4 -4
- package/lib/pipe/testomatio.js +17 -13
- package/lib/reporter-functions.js +1 -3
- package/lib/utils/log-formatter.d.ts +28 -0
- package/lib/utils/log-formatter.js +127 -0
- package/lib/xmlReader.js +14 -4
- package/package.json +2 -2
- package/src/adapter/codecept.js +19 -19
- package/src/adapter/mocha.js +1 -1
- package/src/adapter/playwright.js +2 -2
- package/src/bin/cli.js +1 -1
- package/src/client.js +15 -112
- package/src/junit-adapter/csharp.js +4 -1
- package/src/junit-adapter/nunit-parser.js +9 -7
- package/src/pipe/bitbucket.js +5 -5
- package/src/pipe/debug.js +1 -2
- package/src/pipe/gitlab.js +4 -4
- package/src/pipe/testomatio.js +73 -79
- package/src/reporter-functions.js +2 -3
- package/src/reporter.js +1 -2
- package/src/services/links.js +1 -1
- package/src/utils/log-formatter.js +113 -0
- package/src/xmlReader.js +14 -4
package/lib/adapter/codecept.js
CHANGED
|
@@ -20,11 +20,14 @@ if (!global.codeceptjs) {
|
|
|
20
20
|
}
|
|
21
21
|
// @ts-ignore
|
|
22
22
|
const { event, recorder, codecept, output } = global.codeceptjs;
|
|
23
|
-
const [, MAJOR_VERSION, MINOR_VERSION] = codecept
|
|
23
|
+
const [, MAJOR_VERSION, MINOR_VERSION] = codecept
|
|
24
|
+
.version()
|
|
25
|
+
.match(/(\d+)\.(\d+)/)
|
|
26
|
+
.map(Number);
|
|
24
27
|
// Constants for hook execution order
|
|
25
28
|
const HOOK_EXECUTION_ORDER = {
|
|
26
29
|
PRE_TEST: ['BeforeSuiteHook', 'BeforeHook'],
|
|
27
|
-
POST_TEST: ['AfterHook', 'AfterSuiteHook']
|
|
30
|
+
POST_TEST: ['AfterHook', 'AfterSuiteHook'],
|
|
28
31
|
};
|
|
29
32
|
// codeceptjs workers are self-contained
|
|
30
33
|
data_storage_js_1.dataStorage.isFileStorage = false;
|
|
@@ -92,7 +95,7 @@ function CodeceptReporter(config) {
|
|
|
92
95
|
global.testomatioDataStore = {};
|
|
93
96
|
});
|
|
94
97
|
// Hook event listeners
|
|
95
|
-
event.dispatcher.on(event.hook.started,
|
|
98
|
+
event.dispatcher.on(event.hook.started, hook => {
|
|
96
99
|
output.stepShift = 2;
|
|
97
100
|
currentHook = hook.name;
|
|
98
101
|
let title = hook.hookName;
|
|
@@ -281,7 +284,7 @@ function captureHookStep(step, currentHook, hookSteps) {
|
|
|
281
284
|
status: step.status,
|
|
282
285
|
startTime,
|
|
283
286
|
endTime,
|
|
284
|
-
helperMethod: step.helperMethod
|
|
287
|
+
helperMethod: step.helperMethod,
|
|
285
288
|
});
|
|
286
289
|
hookSteps.set(currentHook, hookStepsArray);
|
|
287
290
|
}
|
|
@@ -369,7 +372,7 @@ function createSectionStep(metaStep) {
|
|
|
369
372
|
category: 'user',
|
|
370
373
|
title: metaStep.toString(), // Use built-in toString method
|
|
371
374
|
duration: metaStep.duration || 0, // Use built-in duration
|
|
372
|
-
steps: []
|
|
375
|
+
steps: [],
|
|
373
376
|
};
|
|
374
377
|
}
|
|
375
378
|
function createHookSection(hookName, steps) {
|
|
@@ -379,7 +382,7 @@ function createHookSection(hookName, steps) {
|
|
|
379
382
|
category: 'hook',
|
|
380
383
|
title: formatHookName(hookName),
|
|
381
384
|
duration: 0,
|
|
382
|
-
steps: []
|
|
385
|
+
steps: [],
|
|
383
386
|
};
|
|
384
387
|
for (const step of steps) {
|
|
385
388
|
const formattedStep = formatHookStep(step);
|
|
@@ -403,13 +406,13 @@ function formatCodeceptStep(step) {
|
|
|
403
406
|
const formattedStep = {
|
|
404
407
|
category,
|
|
405
408
|
title,
|
|
406
|
-
duration
|
|
409
|
+
duration,
|
|
407
410
|
};
|
|
408
411
|
// Add error if step failed
|
|
409
412
|
if (step.status === 'failed' && step.err) {
|
|
410
413
|
formattedStep.error = {
|
|
411
414
|
message: step.err.message || 'Step failed',
|
|
412
|
-
stack: step.err.stack || ''
|
|
415
|
+
stack: step.err.stack || '',
|
|
413
416
|
};
|
|
414
417
|
}
|
|
415
418
|
return formattedStep;
|
|
@@ -430,7 +433,7 @@ function formatHookStep(step) {
|
|
|
430
433
|
return {
|
|
431
434
|
category: 'hook',
|
|
432
435
|
title,
|
|
433
|
-
duration: step.duration || 0
|
|
436
|
+
duration: step.duration || 0,
|
|
434
437
|
};
|
|
435
438
|
}
|
|
436
439
|
module.exports = CodeceptReporter;
|
package/lib/client.d.ts
CHANGED
|
@@ -61,15 +61,5 @@ export class Client {
|
|
|
61
61
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
62
62
|
*/
|
|
63
63
|
updateRunStatus(status: "passed" | "failed" | "skipped" | "finished"): Promise<any>;
|
|
64
|
-
/**
|
|
65
|
-
* Returns the formatted stack including the stack trace, steps, and logs.
|
|
66
|
-
* @returns {string}
|
|
67
|
-
*/
|
|
68
|
-
formatLogs({ error, steps, logs }: {
|
|
69
|
-
error: any;
|
|
70
|
-
steps: any;
|
|
71
|
-
logs: any;
|
|
72
|
-
}): string;
|
|
73
|
-
formatError(error: any, message: any): string;
|
|
74
64
|
}
|
|
75
65
|
import { S3Uploader } from './uploader.js';
|
package/lib/client.js
CHANGED
|
@@ -1,59 +1,22 @@
|
|
|
1
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 () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
38
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
6
|
exports.Client = void 0;
|
|
40
7
|
const debug_1 = __importDefault(require("debug"));
|
|
41
|
-
const callsite_record_1 = __importDefault(require("callsite-record"));
|
|
42
|
-
const minimatch_1 = require("minimatch");
|
|
43
8
|
const fs_1 = __importDefault(require("fs"));
|
|
44
9
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
45
|
-
const crypto_1 = require("crypto");
|
|
46
10
|
const constants_js_1 = require("./constants.js");
|
|
47
11
|
const index_js_1 = require("./pipe/index.js");
|
|
48
12
|
const glob_1 = require("glob");
|
|
49
|
-
const path_1 =
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
50
14
|
const node_url_1 = require("node:url");
|
|
51
15
|
const uploader_js_1 = require("./uploader.js");
|
|
52
16
|
const utils_js_1 = require("./utils/utils.js");
|
|
53
17
|
const filesize_1 = require("filesize");
|
|
54
|
-
const
|
|
18
|
+
const log_formatter_js_1 = require("./utils/log-formatter.js");
|
|
55
19
|
const debug = (0, debug_1.default)('@testomatio/reporter:client');
|
|
56
|
-
const stripColors = util_1.stripVTControlCharacters || ((str) => str?.replace(/\x1b\[[0-9;]*m/g, '') || '');
|
|
57
20
|
// removed __dirname usage, because:
|
|
58
21
|
// 1. replaced with ESM syntax (import.meta.url), but it throws an error on tsc compilation;
|
|
59
22
|
// 2. got error "__dirname already defined" in compiles js code (cjs dir)
|
|
@@ -172,7 +135,7 @@ class Client {
|
|
|
172
135
|
/**
|
|
173
136
|
* @type {TestData}
|
|
174
137
|
*/
|
|
175
|
-
const { rid, error = null, steps: originalSteps, title, suite_title
|
|
138
|
+
const { rid, error = null, steps: originalSteps, title, suite_title } = testData;
|
|
176
139
|
let steps = originalSteps;
|
|
177
140
|
const uploadedFiles = [];
|
|
178
141
|
const stackArtifactsEnabled = (0, utils_js_1.transformEnvVarToBoolean)(process.env.TESTOMATIO_STACK_ARTIFACTS);
|
|
@@ -188,12 +151,16 @@ class Client {
|
|
|
188
151
|
const testContext = suite_title ? `${suite_title} ${title}` : title;
|
|
189
152
|
let errorFormatted = '';
|
|
190
153
|
if (error) {
|
|
191
|
-
errorFormatted +=
|
|
154
|
+
errorFormatted += (0, log_formatter_js_1.formatError)(error) || '';
|
|
192
155
|
message = error?.message;
|
|
193
156
|
}
|
|
194
|
-
let fullLogs =
|
|
157
|
+
let fullLogs = (0, log_formatter_js_1.formatLogs)({ error: errorFormatted, steps, logs: testData.logs });
|
|
195
158
|
if (stackArtifactsEnabled && fullLogs?.trim()?.length > 0) {
|
|
196
|
-
uploadedFiles.push(this.uploader.uploadFileAsBuffer(Buffer.from(stripColors(fullLogs), 'utf8'), [
|
|
159
|
+
uploadedFiles.push(this.uploader.uploadFileAsBuffer(Buffer.from((0, log_formatter_js_1.stripColors)(fullLogs), 'utf8'), [
|
|
160
|
+
this.runId,
|
|
161
|
+
rid,
|
|
162
|
+
`logs_${+new Date()}.log`,
|
|
163
|
+
]));
|
|
197
164
|
fullLogs = '';
|
|
198
165
|
steps = null;
|
|
199
166
|
}
|
|
@@ -205,7 +172,7 @@ class Client {
|
|
|
205
172
|
}
|
|
206
173
|
return [];
|
|
207
174
|
}
|
|
208
|
-
if (
|
|
175
|
+
if (isTestShouldBeExcludedFromReport(testData))
|
|
209
176
|
return [];
|
|
210
177
|
if (status === constants_js_1.STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
|
|
211
178
|
debug('Skipping test from report', testData?.title);
|
|
@@ -332,93 +299,14 @@ class Client {
|
|
|
332
299
|
.catch(err => console.log(constants_js_1.APP_PREFIX, err));
|
|
333
300
|
return this.queue;
|
|
334
301
|
}
|
|
335
|
-
/**
|
|
336
|
-
* Returns the formatted stack including the stack trace, steps, and logs.
|
|
337
|
-
* @returns {string}
|
|
338
|
-
*/
|
|
339
|
-
formatLogs({ error, steps, logs }) {
|
|
340
|
-
error = error?.trim();
|
|
341
|
-
logs = logs?.trim().split('\n').map(l => (0, utils_js_1.truncate)(l)).join('\n');
|
|
342
|
-
if (Array.isArray(steps)) {
|
|
343
|
-
steps = steps
|
|
344
|
-
.map(step => (0, utils_js_1.formatStep)(step))
|
|
345
|
-
.flat()
|
|
346
|
-
.join('\n');
|
|
347
|
-
}
|
|
348
|
-
let testLogs = '';
|
|
349
|
-
if (steps)
|
|
350
|
-
testLogs += `${picocolors_1.default.bold(picocolors_1.default.blue('################[ Steps ]################'))}\n${steps}\n\n`;
|
|
351
|
-
if (logs)
|
|
352
|
-
testLogs += `${picocolors_1.default.bold(picocolors_1.default.gray('################[ Logs ]################'))}\n${logs}\n\n`;
|
|
353
|
-
if (error)
|
|
354
|
-
testLogs += `${picocolors_1.default.bold(picocolors_1.default.red('################[ Failure ]################'))}\n${error}`;
|
|
355
|
-
return testLogs;
|
|
356
|
-
}
|
|
357
|
-
formatError(error, message) {
|
|
358
|
-
if (!message)
|
|
359
|
-
message = error.message;
|
|
360
|
-
if (error.inspect)
|
|
361
|
-
message = error.inspect() || '';
|
|
362
|
-
let stack = '';
|
|
363
|
-
if (error.name)
|
|
364
|
-
stack += `${picocolors_1.default.red(error.name)}`;
|
|
365
|
-
if (error.operator)
|
|
366
|
-
stack += ` (${picocolors_1.default.red(error.operator)})`;
|
|
367
|
-
// add new line if something was added to stack
|
|
368
|
-
if (stack)
|
|
369
|
-
stack += ': ';
|
|
370
|
-
stack += `${message}\n`;
|
|
371
|
-
if (error.diff) {
|
|
372
|
-
// diff for vitest
|
|
373
|
-
stack += error.diff;
|
|
374
|
-
stack += '\n\n';
|
|
375
|
-
}
|
|
376
|
-
else if (error.actual && error.expected && error.actual !== error.expected) {
|
|
377
|
-
// diffs for mocha, cypress, codeceptjs style
|
|
378
|
-
stack += `\n\n${picocolors_1.default.bold(picocolors_1.default.green('+ expected'))} ${picocolors_1.default.bold(picocolors_1.default.red('- actual'))}`;
|
|
379
|
-
stack += `\n${picocolors_1.default.green(`+ ${error.expected.toString().split('\n').join('\n+ ')}`)}`;
|
|
380
|
-
stack += `\n${picocolors_1.default.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
|
|
381
|
-
stack += '\n\n';
|
|
382
|
-
}
|
|
383
|
-
const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
|
|
384
|
-
try {
|
|
385
|
-
let hasFrame = false;
|
|
386
|
-
const record = (0, callsite_record_1.default)({
|
|
387
|
-
forError: error,
|
|
388
|
-
isCallsiteFrame: frame => {
|
|
389
|
-
if (customFilter && (0, minimatch_1.minimatch)(frame.fileName, customFilter))
|
|
390
|
-
return false;
|
|
391
|
-
if (hasFrame)
|
|
392
|
-
return false;
|
|
393
|
-
if (isNotInternalFrame(frame))
|
|
394
|
-
hasFrame = true;
|
|
395
|
-
return hasFrame;
|
|
396
|
-
},
|
|
397
|
-
});
|
|
398
|
-
// @ts-ignore
|
|
399
|
-
if (record && !record.filename.startsWith('http')) {
|
|
400
|
-
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
401
|
-
}
|
|
402
|
-
return stack;
|
|
403
|
-
}
|
|
404
|
-
catch (e) {
|
|
405
|
-
console.log(e);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
302
|
}
|
|
409
303
|
exports.Client = Client;
|
|
410
|
-
function isNotInternalFrame(frame) {
|
|
411
|
-
return (frame.getFileName() &&
|
|
412
|
-
frame.getFileName().includes(path_1.sep) &&
|
|
413
|
-
!frame.getFileName().includes('node_modules') &&
|
|
414
|
-
!frame.getFileName().includes('internal'));
|
|
415
|
-
}
|
|
416
304
|
/**
|
|
417
305
|
*
|
|
418
306
|
* @param {TestData} testData
|
|
419
307
|
* @returns boolean
|
|
420
308
|
*/
|
|
421
|
-
function
|
|
309
|
+
function isTestShouldBeExcludedFromReport(testData) {
|
|
422
310
|
// const fileName = path.basename(test.location?.file || '');
|
|
423
311
|
const globExcludeFilesPattern = process.env.TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN;
|
|
424
312
|
if (!globExcludeFilesPattern)
|
|
@@ -427,11 +315,11 @@ function isTestShouldBeExculedFromReport(testData) {
|
|
|
427
315
|
debug('No "file" property found for test ', testData.title);
|
|
428
316
|
return false;
|
|
429
317
|
}
|
|
430
|
-
const
|
|
318
|
+
const excludePatternsList = globExcludeFilesPattern.split(';');
|
|
431
319
|
// as scanning files is time consuming operation, just save the result in variable to avoid multiple scans
|
|
432
320
|
if (!listOfTestFilesToExcludeFromReport) {
|
|
433
321
|
// list of files with relative paths
|
|
434
|
-
listOfTestFilesToExcludeFromReport = glob_1.glob.sync(
|
|
322
|
+
listOfTestFilesToExcludeFromReport = glob_1.glob.sync(excludePatternsList, { ignore: '**/node_modules/**' });
|
|
435
323
|
debug('Tests from next files will not be reported:', listOfTestFilesToExcludeFromReport);
|
|
436
324
|
}
|
|
437
325
|
const testFileRelativePath = path_1.default.relative(process.cwd(), testData.file);
|
|
@@ -12,7 +12,10 @@ class CSharpAdapter extends adapter_js_1.default {
|
|
|
12
12
|
const exampleMatch = t.title.match(/\((.*?)\)/);
|
|
13
13
|
if (exampleMatch) {
|
|
14
14
|
// Extract parameters as object with numeric keys for API
|
|
15
|
-
const params = exampleMatch[1]
|
|
15
|
+
const params = exampleMatch[1]
|
|
16
|
+
.split(',')
|
|
17
|
+
.map(param => param.trim())
|
|
18
|
+
.filter(param => param !== '');
|
|
16
19
|
t.example = {};
|
|
17
20
|
params.forEach((param, index) => {
|
|
18
21
|
t.example[index] = param;
|
|
@@ -359,13 +359,15 @@ class NUnitXmlParser {
|
|
|
359
359
|
parameters.push(current.trim());
|
|
360
360
|
}
|
|
361
361
|
// Clean up parameters - remove quotes if they wrap the entire parameter and filter empty ones
|
|
362
|
-
return parameters
|
|
362
|
+
return parameters
|
|
363
|
+
.map(param => {
|
|
363
364
|
param = param.trim();
|
|
364
365
|
if ((param.startsWith('"') && param.endsWith('"')) || (param.startsWith("'") && param.endsWith("'"))) {
|
|
365
366
|
return param.slice(1, -1);
|
|
366
367
|
}
|
|
367
368
|
return param;
|
|
368
|
-
})
|
|
369
|
+
})
|
|
370
|
+
.filter(p => !!p);
|
|
369
371
|
}
|
|
370
372
|
/**
|
|
371
373
|
* Extract method name from test name (fallback)
|
package/lib/pipe/bitbucket.js
CHANGED
|
@@ -73,8 +73,8 @@ class BitbucketPipe {
|
|
|
73
73
|
baseURL: 'https://api.bitbucket.org/2.0',
|
|
74
74
|
headers: {
|
|
75
75
|
'Content-Type': 'application/json',
|
|
76
|
-
|
|
77
|
-
}
|
|
76
|
+
Authorization: `Bearer ${this.token}`,
|
|
77
|
+
},
|
|
78
78
|
});
|
|
79
79
|
debug('Bitbucket Pipe: Enabled');
|
|
80
80
|
}
|
|
@@ -185,7 +185,7 @@ class BitbucketPipe {
|
|
|
185
185
|
const addCommentResponse = await this.client.request({
|
|
186
186
|
method: 'POST',
|
|
187
187
|
url: commentsRequestURL,
|
|
188
|
-
data: { content: { raw: body } }
|
|
188
|
+
data: { content: { raw: body } },
|
|
189
189
|
});
|
|
190
190
|
const commentID = addCommentResponse.data.id;
|
|
191
191
|
// eslint-disable-next-line max-len
|
|
@@ -212,7 +212,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
|
|
|
212
212
|
try {
|
|
213
213
|
const response = await client.request({
|
|
214
214
|
method: 'GET',
|
|
215
|
-
url: commentsRequestURL
|
|
215
|
+
url: commentsRequestURL,
|
|
216
216
|
});
|
|
217
217
|
comments = response.data.values;
|
|
218
218
|
}
|
|
@@ -229,7 +229,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
|
|
|
229
229
|
const deleteCommentURL = `${commentsRequestURL}/${comment.id}`;
|
|
230
230
|
await client.request({
|
|
231
231
|
method: 'DELETE',
|
|
232
|
-
url: deleteCommentURL
|
|
232
|
+
url: deleteCommentURL,
|
|
233
233
|
});
|
|
234
234
|
}
|
|
235
235
|
catch (e) {
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -43,7 +43,7 @@ class GitLabPipe {
|
|
|
43
43
|
baseURL: 'https://gitlab.com/api/v4',
|
|
44
44
|
headers: {
|
|
45
45
|
'Content-Type': 'application/json',
|
|
46
|
-
}
|
|
46
|
+
},
|
|
47
47
|
});
|
|
48
48
|
debug('GitLab Pipe: Enabled');
|
|
49
49
|
}
|
|
@@ -142,7 +142,7 @@ class GitLabPipe {
|
|
|
142
142
|
method: 'POST',
|
|
143
143
|
url: commentsRequestURL,
|
|
144
144
|
params: { access_token: this.token },
|
|
145
|
-
data: { body }
|
|
145
|
+
data: { body },
|
|
146
146
|
});
|
|
147
147
|
const commentID = addCommentResponse.data.id;
|
|
148
148
|
// eslint-disable-next-line max-len
|
|
@@ -169,7 +169,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
|
|
|
169
169
|
const response = await client.request({
|
|
170
170
|
method: 'GET',
|
|
171
171
|
url: commentsRequestURL,
|
|
172
|
-
params: { access_token: token }
|
|
172
|
+
params: { access_token: token },
|
|
173
173
|
});
|
|
174
174
|
comments = response.data;
|
|
175
175
|
}
|
|
@@ -187,7 +187,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
|
|
|
187
187
|
await client.request({
|
|
188
188
|
method: 'DELETE',
|
|
189
189
|
url: deleteCommentURL,
|
|
190
|
-
params: { access_token: token }
|
|
190
|
+
params: { access_token: token },
|
|
191
191
|
});
|
|
192
192
|
}
|
|
193
193
|
catch (e) {
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -60,7 +60,7 @@ class TestomatioPipe {
|
|
|
60
60
|
retry: constants_js_1.REPORTER_REQUEST_RETRIES.retriesPerRequest,
|
|
61
61
|
retryDelay: constants_js_1.REPORTER_REQUEST_RETRIES.retryTimeout,
|
|
62
62
|
httpMethodsToRetry: ['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE', 'POST'],
|
|
63
|
-
shouldRetry:
|
|
63
|
+
shouldRetry: error => {
|
|
64
64
|
if (!error.response)
|
|
65
65
|
return false;
|
|
66
66
|
switch (error.response?.status) {
|
|
@@ -73,8 +73,8 @@ class TestomatioPipe {
|
|
|
73
73
|
break;
|
|
74
74
|
}
|
|
75
75
|
return error.response?.status >= 401; // Retry on 401+ and 5xx
|
|
76
|
-
}
|
|
77
|
-
}
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
78
|
});
|
|
79
79
|
this.isEnabled = true;
|
|
80
80
|
// do not finish this run (for parallel testing)
|
|
@@ -194,7 +194,7 @@ class TestomatioPipe {
|
|
|
194
194
|
method: 'PUT',
|
|
195
195
|
url: `/api/reporter/${this.runId}`,
|
|
196
196
|
data: runParams,
|
|
197
|
-
responseType: 'json'
|
|
197
|
+
responseType: 'json',
|
|
198
198
|
});
|
|
199
199
|
if (resp.data.artifacts)
|
|
200
200
|
(0, pipe_utils_js_1.setS3Credentials)(resp.data.artifacts);
|
|
@@ -207,7 +207,7 @@ class TestomatioPipe {
|
|
|
207
207
|
url: '/api/reporter',
|
|
208
208
|
data: runParams,
|
|
209
209
|
maxContentLength: Infinity,
|
|
210
|
-
responseType: 'json'
|
|
210
|
+
responseType: 'json',
|
|
211
211
|
});
|
|
212
212
|
this.runId = resp.data.uid;
|
|
213
213
|
this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
|
|
@@ -260,15 +260,17 @@ class TestomatioPipe {
|
|
|
260
260
|
this.#formatData(data);
|
|
261
261
|
const json = json_cycle_1.default.stringify(data);
|
|
262
262
|
debug('Adding test', json);
|
|
263
|
-
return this.client
|
|
263
|
+
return this.client
|
|
264
|
+
.request({
|
|
264
265
|
method: 'POST',
|
|
265
266
|
url: `/api/reporter/${this.runId}/testrun`,
|
|
266
267
|
data: json,
|
|
267
268
|
headers: {
|
|
268
269
|
'Content-Type': 'application/json',
|
|
269
270
|
},
|
|
270
|
-
maxContentLength: Infinity
|
|
271
|
-
})
|
|
271
|
+
maxContentLength: Infinity,
|
|
272
|
+
})
|
|
273
|
+
.catch(err => {
|
|
272
274
|
this.requestFailures++;
|
|
273
275
|
this.notReportedTestsCount++;
|
|
274
276
|
if (err.response) {
|
|
@@ -313,19 +315,21 @@ class TestomatioPipe {
|
|
|
313
315
|
// get tests from batch and clear batch
|
|
314
316
|
const testsToSend = this.batch.tests.splice(0);
|
|
315
317
|
debug('📨 Batch upload', testsToSend.length, 'tests');
|
|
316
|
-
return this.client
|
|
318
|
+
return this.client
|
|
319
|
+
.request({
|
|
317
320
|
method: 'POST',
|
|
318
321
|
url: `/api/reporter/${this.runId}/testrun`,
|
|
319
322
|
data: {
|
|
320
323
|
api_key: this.apiKey,
|
|
321
324
|
tests: testsToSend,
|
|
322
|
-
batch_index: this.batch.batchIndex
|
|
325
|
+
batch_index: this.batch.batchIndex,
|
|
323
326
|
},
|
|
324
327
|
headers: {
|
|
325
328
|
'Content-Type': 'application/json',
|
|
326
329
|
},
|
|
327
|
-
maxContentLength: Infinity
|
|
328
|
-
})
|
|
330
|
+
maxContentLength: Infinity,
|
|
331
|
+
})
|
|
332
|
+
.catch(err => {
|
|
329
333
|
this.requestFailures++;
|
|
330
334
|
this.notReportedTestsCount += testsToSend.length;
|
|
331
335
|
if (err.response) {
|
|
@@ -409,7 +413,7 @@ class TestomatioPipe {
|
|
|
409
413
|
status_event,
|
|
410
414
|
detach: params.detach,
|
|
411
415
|
tests: params.tests,
|
|
412
|
-
}
|
|
416
|
+
},
|
|
413
417
|
});
|
|
414
418
|
if (this.runUrl) {
|
|
415
419
|
console.log(constants_js_1.APP_PREFIX, '📊 Report Saved. Report URL:', picocolors_1.default.magenta(this.runUrl));
|
|
@@ -59,9 +59,7 @@ function setLabel(key, value = null) {
|
|
|
59
59
|
if (Array.isArray(value)) {
|
|
60
60
|
return value.forEach(label => setLabel(key, label));
|
|
61
61
|
}
|
|
62
|
-
const labelObject = value !== null && value !== undefined && value !== ''
|
|
63
|
-
? { label: `${key}:${value}` }
|
|
64
|
-
: { label: key };
|
|
62
|
+
const labelObject = value !== null && value !== undefined && value !== '' ? { label: `${key}:${value}` } : { label: key };
|
|
65
63
|
index_js_1.services.links.put([labelObject]);
|
|
66
64
|
}
|
|
67
65
|
/**
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the formatted stack including the stack trace, steps, and logs.
|
|
3
|
+
* @param {Object} params - Parameters for formatting logs
|
|
4
|
+
* @param {string} params.error - Error message
|
|
5
|
+
* @param {Array|any} params.steps - Test steps (array or other types)
|
|
6
|
+
* @param {string} params.logs - Test logs
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function formatLogs({ error, steps, logs }: {
|
|
10
|
+
error: string;
|
|
11
|
+
steps: any[] | any;
|
|
12
|
+
logs: string;
|
|
13
|
+
}): string;
|
|
14
|
+
/**
|
|
15
|
+
* Formats an error with stack trace and diff information
|
|
16
|
+
* @param {Error & {inspect?: () => string, operator?: string, diff?: string, actual?: any, expected?: any}} error
|
|
17
|
+
* The error object to format
|
|
18
|
+
* @param {string} [message] - Optional error message override
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
export function formatError(error: Error & {
|
|
22
|
+
inspect?: () => string;
|
|
23
|
+
operator?: string;
|
|
24
|
+
diff?: string;
|
|
25
|
+
actual?: any;
|
|
26
|
+
expected?: any;
|
|
27
|
+
}, message?: string): string;
|
|
28
|
+
export function stripColors(str: string): string;
|
|
@@ -0,0 +1,127 @@
|
|
|
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.stripColors = void 0;
|
|
7
|
+
exports.formatLogs = formatLogs;
|
|
8
|
+
exports.formatError = formatError;
|
|
9
|
+
const callsite_record_1 = __importDefault(require("callsite-record"));
|
|
10
|
+
const minimatch_1 = require("minimatch");
|
|
11
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
12
|
+
const util_1 = require("util");
|
|
13
|
+
const path_1 = require("path");
|
|
14
|
+
const utils_js_1 = require("./utils.js");
|
|
15
|
+
const stripColors = util_1.stripVTControlCharacters || (str => str?.replace(/\x1b\[[0-9;]*m/g, '') || '');
|
|
16
|
+
exports.stripColors = stripColors;
|
|
17
|
+
/**
|
|
18
|
+
* Returns the formatted stack including the stack trace, steps, and logs.
|
|
19
|
+
* @param {Object} params - Parameters for formatting logs
|
|
20
|
+
* @param {string} params.error - Error message
|
|
21
|
+
* @param {Array|any} params.steps - Test steps (array or other types)
|
|
22
|
+
* @param {string} params.logs - Test logs
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
function formatLogs({ error, steps, logs }) {
|
|
26
|
+
error = error?.trim();
|
|
27
|
+
logs = logs
|
|
28
|
+
?.trim()
|
|
29
|
+
.split('\n')
|
|
30
|
+
.map(l => (0, utils_js_1.truncate)(l))
|
|
31
|
+
.join('\n');
|
|
32
|
+
if (Array.isArray(steps)) {
|
|
33
|
+
steps = steps
|
|
34
|
+
.map(step => (0, utils_js_1.formatStep)(step))
|
|
35
|
+
.flat()
|
|
36
|
+
.join('\n');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
steps = null;
|
|
40
|
+
}
|
|
41
|
+
let testLogs = '';
|
|
42
|
+
if (steps)
|
|
43
|
+
testLogs += `${picocolors_1.default.bold(picocolors_1.default.blue('################[ Steps ]################'))}\n${steps}\n\n`;
|
|
44
|
+
if (logs)
|
|
45
|
+
testLogs += `${picocolors_1.default.bold(picocolors_1.default.gray('################[ Logs ]################'))}\n${logs}\n\n`;
|
|
46
|
+
if (error)
|
|
47
|
+
testLogs += `${picocolors_1.default.bold(picocolors_1.default.red('################[ Failure ]################'))}\n${error}`;
|
|
48
|
+
return testLogs;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Formats an error with stack trace and diff information
|
|
52
|
+
* @param {Error & {inspect?: () => string, operator?: string, diff?: string, actual?: any, expected?: any}} error
|
|
53
|
+
* The error object to format
|
|
54
|
+
* @param {string} [message] - Optional error message override
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
function formatError(error, message) {
|
|
58
|
+
if (!message)
|
|
59
|
+
message = error.message;
|
|
60
|
+
// @ts-ignore - inspect is a custom property added by some testing frameworks
|
|
61
|
+
if (error.inspect)
|
|
62
|
+
message = error.inspect() || '';
|
|
63
|
+
let stack = '';
|
|
64
|
+
if (error.name)
|
|
65
|
+
stack += `${picocolors_1.default.red(error.name)}`;
|
|
66
|
+
// @ts-ignore - operator is a custom property added by assertion libraries
|
|
67
|
+
if (error.operator)
|
|
68
|
+
stack += ` (${picocolors_1.default.red(error.operator)})`;
|
|
69
|
+
// add new line if something was added to stack
|
|
70
|
+
if (stack)
|
|
71
|
+
stack += ': ';
|
|
72
|
+
stack += `${message}\n`;
|
|
73
|
+
// @ts-ignore - diff is a custom property added by vitest
|
|
74
|
+
if (error.diff) {
|
|
75
|
+
// diff for vitest
|
|
76
|
+
stack += error.diff;
|
|
77
|
+
stack += '\n\n';
|
|
78
|
+
}
|
|
79
|
+
else if (error.actual && error.expected && error.actual !== error.expected) {
|
|
80
|
+
// diffs for mocha, cypress, codeceptjs style
|
|
81
|
+
stack += `\n\n${picocolors_1.default.bold(picocolors_1.default.green('+ expected'))} ${picocolors_1.default.bold(picocolors_1.default.red('- actual'))}`;
|
|
82
|
+
stack += `\n${picocolors_1.default.green(`+ ${error.expected.toString().split('\n').join('\n+ ')}`)}`;
|
|
83
|
+
stack += `\n${picocolors_1.default.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
|
|
84
|
+
stack += '\n\n';
|
|
85
|
+
}
|
|
86
|
+
const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
|
|
87
|
+
try {
|
|
88
|
+
let hasFrame = false;
|
|
89
|
+
const record = (0, callsite_record_1.default)({
|
|
90
|
+
forError: error,
|
|
91
|
+
isCallsiteFrame: frame => {
|
|
92
|
+
if (customFilter && (0, minimatch_1.minimatch)(frame.fileName, customFilter))
|
|
93
|
+
return false;
|
|
94
|
+
if (hasFrame)
|
|
95
|
+
return false;
|
|
96
|
+
if (isNotInternalFrame(frame))
|
|
97
|
+
hasFrame = true;
|
|
98
|
+
return hasFrame;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
if (record && !record.filename.startsWith('http')) {
|
|
103
|
+
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
104
|
+
}
|
|
105
|
+
return stack;
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
console.log(e);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Checks if a stack frame is not an internal frame (node_modules or internal)
|
|
113
|
+
* @param {Object} frame - Stack frame object
|
|
114
|
+
* @returns {boolean}
|
|
115
|
+
*/
|
|
116
|
+
function isNotInternalFrame(frame) {
|
|
117
|
+
return (frame.getFileName() &&
|
|
118
|
+
frame.getFileName().includes(path_1.sep) &&
|
|
119
|
+
!frame.getFileName().includes('node_modules') &&
|
|
120
|
+
!frame.getFileName().includes('internal'));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports.formatLogs = formatLogs;
|
|
124
|
+
|
|
125
|
+
module.exports.formatError = formatError;
|
|
126
|
+
|
|
127
|
+
module.exports.stripColors = stripColors;
|