@testomatio/reporter 2.3.8-rc.1 β 2.3.9-beta-bin-fix
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 +1 -1
- package/lib/bin/cli.js +3 -13
- package/lib/bin/reportXml.js +0 -0
- package/lib/bin/startTest.js +0 -0
- package/lib/bin/uploadArtifacts.js +0 -0
- package/lib/client.d.ts +1 -1
- package/lib/client.js +22 -31
- package/lib/pipe/testomatio.d.ts +1 -2
- package/lib/pipe/testomatio.js +1 -2
- package/lib/reporter.d.ts +9 -19
- package/lib/reporter.js +5 -40
- package/lib/utils/utils.js +3 -7
- package/package.json +5 -9
- package/src/bin/cli.js +1 -1
- package/src/bin/reportXml.js +2 -5
- package/src/client.js +26 -54
- package/src/junit-adapter/csharp.js +6 -45
- package/src/reporter.js +4 -7
- package/src/uploader.js +0 -5
- package/src/utils/utils.js +22 -213
- package/src/xmlReader.js +45 -131
- package/src/junit-adapter/nunit-parser.js +0 -462
- package/types/types.d.ts +0 -364
- package/types/vitest.types.d.ts +0 -93
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Testomat.io Reporter (this npm package) supports:
|
|
|
13
13
|
- π [Stack traces](./docs/stacktrace.md) and error messages
|
|
14
14
|
- π [GitHub](./docs/pipes/github.md), [GitLab](./docs/pipes/gitlab.md) & [Bitbucket](./docs/pipes/bitbucket.md) integration
|
|
15
15
|
- π
Realtime reports
|
|
16
|
-
- ποΈ Other test frameworks supported via [
|
|
16
|
+
- ποΈ Other test frameworks supported via [JUNit XML](./docs/junit.md)
|
|
17
17
|
- πΆββοΈ Steps _(work in progress)_
|
|
18
18
|
- π [Logger](./docs/logger.md) _(work in progress, supports Jest for now)_
|
|
19
19
|
- βοΈ Custom properties and metadata _(work in progress)_
|
package/lib/bin/cli.js
CHANGED
|
@@ -37,17 +37,12 @@ program
|
|
|
37
37
|
program
|
|
38
38
|
.command('start')
|
|
39
39
|
.description('Start a new run and return its ID')
|
|
40
|
-
.
|
|
41
|
-
.action(async (opts) => {
|
|
40
|
+
.action(async () => {
|
|
42
41
|
(0, utils_js_1.cleanLatestRunId)();
|
|
43
42
|
console.log('Starting a new Run on Testomat.io...');
|
|
44
43
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config_js_1.config.TESTOMATIO;
|
|
45
44
|
const client = new client_js_1.default({ apiKey });
|
|
46
|
-
|
|
47
|
-
if (opts.kind) {
|
|
48
|
-
createRunParams.kind = opts.kind;
|
|
49
|
-
}
|
|
50
|
-
client.createRun(createRunParams).then(() => {
|
|
45
|
+
client.createRun().then(() => {
|
|
51
46
|
console.log(process.env.runId);
|
|
52
47
|
process.exit(0);
|
|
53
48
|
});
|
|
@@ -75,7 +70,6 @@ program
|
|
|
75
70
|
.description('Run tests with the specified command')
|
|
76
71
|
.argument('<command>', 'Test runner command')
|
|
77
72
|
.option('--filter <filter>', 'Additional execution filter')
|
|
78
|
-
.option('--kind <type>', 'Specify run type: automated, manual, or mixed')
|
|
79
73
|
.action(async (command, opts) => {
|
|
80
74
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config_js_1.config.TESTOMATIO;
|
|
81
75
|
const title = process.env.TESTOMATIO_TITLE;
|
|
@@ -114,12 +108,8 @@ program
|
|
|
114
108
|
process.exit(code);
|
|
115
109
|
});
|
|
116
110
|
};
|
|
117
|
-
const createRunParams = {};
|
|
118
|
-
if (opts.kind) {
|
|
119
|
-
createRunParams.kind = opts.kind;
|
|
120
|
-
}
|
|
121
111
|
if (apiKey) {
|
|
122
|
-
await client.createRun(
|
|
112
|
+
await client.createRun().then(runTests);
|
|
123
113
|
}
|
|
124
114
|
else {
|
|
125
115
|
await runTests();
|
package/lib/bin/reportXml.js
CHANGED
|
File without changes
|
package/lib/bin/startTest.js
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/client.d.ts
CHANGED
package/lib/client.js
CHANGED
|
@@ -51,9 +51,7 @@ const node_url_1 = require("node:url");
|
|
|
51
51
|
const uploader_js_1 = require("./uploader.js");
|
|
52
52
|
const utils_js_1 = require("./utils/utils.js");
|
|
53
53
|
const filesize_1 = require("filesize");
|
|
54
|
-
const util_1 = require("util");
|
|
55
54
|
const debug = (0, debug_1.default)('@testomatio/reporter:client');
|
|
56
|
-
const stripColors = util_1.stripVTControlCharacters || ((str) => str?.replace(/\x1b\[[0-9;]*m/g, '') || '');
|
|
57
55
|
// removed __dirname usage, because:
|
|
58
56
|
// 1. replaced with ESM syntax (import.meta.url), but it throws an error on tsc compilation;
|
|
59
57
|
// 2. got error "__dirname already defined" in compiles js code (cjs dir)
|
|
@@ -131,7 +129,7 @@ class Client {
|
|
|
131
129
|
*
|
|
132
130
|
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
133
131
|
*/
|
|
134
|
-
async createRun(params
|
|
132
|
+
async createRun(params) {
|
|
135
133
|
if (!this.pipes || !this.pipes.length)
|
|
136
134
|
this.pipes = await (0, index_js_1.pipesFactory)(params || this.paramsForPipesFactory || {}, this.pipeStore);
|
|
137
135
|
debug('Creating run...');
|
|
@@ -139,7 +137,7 @@ class Client {
|
|
|
139
137
|
if (!this.pipes?.filter(p => p.isEnabled).length)
|
|
140
138
|
return Promise.resolve();
|
|
141
139
|
this.queue = this.queue
|
|
142
|
-
.then(() => Promise.all(this.pipes.map(p => p.createRun(
|
|
140
|
+
.then(() => Promise.all(this.pipes.map(p => p.createRun())))
|
|
143
141
|
.catch(err => console.log(constants_js_1.APP_PREFIX, err))
|
|
144
142
|
.then(() => {
|
|
145
143
|
const runId = this.pipeStore?.runId;
|
|
@@ -160,6 +158,17 @@ class Client {
|
|
|
160
158
|
* @returns {Promise<PipeResult[]>}
|
|
161
159
|
*/
|
|
162
160
|
async addTestRun(status, testData) {
|
|
161
|
+
if (!this.pipes || !this.pipes.length)
|
|
162
|
+
this.pipes = await (0, index_js_1.pipesFactory)(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
163
|
+
// all pipes disabled, skipping
|
|
164
|
+
if (!this.pipes?.filter(p => p.isEnabled).length)
|
|
165
|
+
return [];
|
|
166
|
+
if (isTestShouldBeExculedFromReport(testData))
|
|
167
|
+
return [];
|
|
168
|
+
if (status === constants_js_1.STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
|
|
169
|
+
debug('Skipping test from report', testData?.title);
|
|
170
|
+
return []; // do not log skipped tests
|
|
171
|
+
}
|
|
163
172
|
if (!testData)
|
|
164
173
|
testData = {
|
|
165
174
|
title: 'Unknown test',
|
|
@@ -172,12 +181,9 @@ class Client {
|
|
|
172
181
|
/**
|
|
173
182
|
* @type {TestData}
|
|
174
183
|
*/
|
|
175
|
-
const { rid, error = null, steps
|
|
176
|
-
let steps = originalSteps;
|
|
177
|
-
const uploadedFiles = [];
|
|
178
|
-
const stackArtifactsEnabled = (0, utils_js_1.transformEnvVarToBoolean)(process.env.TESTOMATIO_STACK_ARTIFACTS);
|
|
179
|
-
const { time = 0, example = null, files = [], filesBuffers = [], code = null, file, suite_id, test_id, timestamp, links, manuallyAttachedArtifacts, overwrite, tags, } = testData;
|
|
184
|
+
const { rid, error = null, time = 0, example = null, files = [], filesBuffers = [], steps, code = null, title, file, suite_title, suite_id, test_id, timestamp, links, manuallyAttachedArtifacts, overwrite, tags, } = testData;
|
|
180
185
|
let { message = '', meta = {} } = testData;
|
|
186
|
+
// stringify meta values and limit keys and values length to 255
|
|
181
187
|
meta = Object.entries(meta)
|
|
182
188
|
.filter(([, value]) => value !== null && value !== undefined)
|
|
183
189
|
.reduce((acc, [key, value]) => {
|
|
@@ -185,34 +191,19 @@ class Client {
|
|
|
185
191
|
acc[key] = value;
|
|
186
192
|
return acc;
|
|
187
193
|
}, {});
|
|
194
|
+
// Get links from storage using the test context
|
|
188
195
|
const testContext = suite_title ? `${suite_title} ${title}` : title;
|
|
189
196
|
let errorFormatted = '';
|
|
190
197
|
if (error) {
|
|
191
198
|
errorFormatted += this.formatError(error) || '';
|
|
192
199
|
message = error?.message;
|
|
193
200
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
fullLogs = '';
|
|
198
|
-
steps = null;
|
|
199
|
-
}
|
|
200
|
-
if (!this.pipes || !this.pipes.length)
|
|
201
|
-
this.pipes = await (0, index_js_1.pipesFactory)(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
202
|
-
if (!this.pipes?.filter(p => p.isEnabled).length) {
|
|
203
|
-
if (uploadedFiles.length > 0) {
|
|
204
|
-
await Promise.all(uploadedFiles);
|
|
205
|
-
}
|
|
206
|
-
return [];
|
|
207
|
-
}
|
|
208
|
-
if (isTestShouldBeExculedFromReport(testData))
|
|
209
|
-
return [];
|
|
210
|
-
if (status === constants_js_1.STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
|
|
211
|
-
debug('Skipping test from report', testData?.title);
|
|
212
|
-
return [];
|
|
213
|
-
}
|
|
201
|
+
// Attach logs
|
|
202
|
+
const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
203
|
+
// add artifacts
|
|
214
204
|
if (manuallyAttachedArtifacts?.length)
|
|
215
205
|
files.push(...manuallyAttachedArtifacts);
|
|
206
|
+
const uploadedFiles = [];
|
|
216
207
|
for (let f of files) {
|
|
217
208
|
if (!f)
|
|
218
209
|
continue; // f === null
|
|
@@ -294,7 +285,7 @@ class Client {
|
|
|
294
285
|
const uploadedArtifacts = this.uploader.successfulUploads.map(file => ({
|
|
295
286
|
relativePath: file.path.replace(process.cwd(), ''),
|
|
296
287
|
link: file.link,
|
|
297
|
-
sizePretty:
|
|
288
|
+
sizePretty: (0, filesize_1.filesize)(file.size, { round: 0 }).toString(),
|
|
298
289
|
}));
|
|
299
290
|
uploadedArtifacts.forEach(upload => {
|
|
300
291
|
debug(`π’Uploaded artifact`, `${upload.relativePath},`, 'size:', `${upload.sizePretty},`, 'link:', `${upload.link}`);
|
|
@@ -304,7 +295,7 @@ class Client {
|
|
|
304
295
|
console.log(constants_js_1.APP_PREFIX, `ποΈ ${this.uploader.failedUploads.length} artifacts π΄${picocolors_1.default.bold('failed')} to upload`);
|
|
305
296
|
const failedUploads = this.uploader.failedUploads.map(file => ({
|
|
306
297
|
relativePath: file.path.replace(process.cwd(), ''),
|
|
307
|
-
sizePretty:
|
|
298
|
+
sizePretty: (0, filesize_1.filesize)(file.size, { round: 0 }).toString(),
|
|
308
299
|
}));
|
|
309
300
|
const pathPadding = Math.max(...failedUploads.map(upload => upload.relativePath.length)) + 1;
|
|
310
301
|
failedUploads.forEach(upload => {
|
package/lib/pipe/testomatio.d.ts
CHANGED
|
@@ -47,12 +47,11 @@ declare class TestomatioPipe implements Pipe {
|
|
|
47
47
|
prepareRun(opts: any): Promise<string[]>;
|
|
48
48
|
/**
|
|
49
49
|
* Creates a new run on Testomat.io
|
|
50
|
-
* @param {{isBatchEnabled?: boolean
|
|
50
|
+
* @param {{isBatchEnabled?: boolean}} params
|
|
51
51
|
* @returns Promise<void>
|
|
52
52
|
*/
|
|
53
53
|
createRun(params?: {
|
|
54
54
|
isBatchEnabled?: boolean;
|
|
55
|
-
kind?: string;
|
|
56
55
|
}): Promise<void>;
|
|
57
56
|
runUrl: string;
|
|
58
57
|
runPublicUrl: any;
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -148,7 +148,7 @@ class TestomatioPipe {
|
|
|
148
148
|
}
|
|
149
149
|
/**
|
|
150
150
|
* Creates a new run on Testomat.io
|
|
151
|
-
* @param {{isBatchEnabled?: boolean
|
|
151
|
+
* @param {{isBatchEnabled?: boolean}} params
|
|
152
152
|
* @returns Promise<void>
|
|
153
153
|
*/
|
|
154
154
|
async createRun(params = {}) {
|
|
@@ -184,7 +184,6 @@ class TestomatioPipe {
|
|
|
184
184
|
label: this.label,
|
|
185
185
|
shared_run: this.sharedRun,
|
|
186
186
|
shared_run_timeout: this.sharedRunTimeout,
|
|
187
|
-
kind: params.kind,
|
|
188
187
|
}).filter(([, value]) => !!value));
|
|
189
188
|
debug(' >>>>>> Run params', JSON.stringify(runParams, null, 2));
|
|
190
189
|
if (this.runId) {
|
package/lib/reporter.d.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
export { Client };
|
|
2
|
-
export const STATUS: {
|
|
3
|
-
PASSED: string;
|
|
4
|
-
FAILED: string;
|
|
5
|
-
SKIPPED: string;
|
|
6
|
-
FINISHED: string;
|
|
7
|
-
};
|
|
8
1
|
export const artifact: (data: string | {
|
|
9
2
|
path: string;
|
|
10
3
|
type: string;
|
|
@@ -87,7 +80,7 @@ export const label: (key: string, value?: string | null) => void;
|
|
|
87
80
|
export const linkTest: (...testIds: string[]) => void;
|
|
88
81
|
export const linkJira: (...jiraIds: string[]) => void;
|
|
89
82
|
declare namespace _default {
|
|
90
|
-
|
|
83
|
+
let testomatioLogger: {
|
|
91
84
|
"__#13@#originalUserLogger": {
|
|
92
85
|
assert(condition?: boolean, ...data: any[]): void;
|
|
93
86
|
assert(value: any, message?: string, ...optionalParams: any[]): void;
|
|
@@ -155,13 +148,13 @@ declare namespace _default {
|
|
|
155
148
|
}): void;
|
|
156
149
|
prettyObjects: boolean;
|
|
157
150
|
};
|
|
158
|
-
|
|
151
|
+
let artifact: (data: string | {
|
|
159
152
|
path: string;
|
|
160
153
|
type: string;
|
|
161
154
|
name: string;
|
|
162
155
|
}, context?: any) => void;
|
|
163
|
-
|
|
164
|
-
|
|
156
|
+
let log: (...args: any[]) => void;
|
|
157
|
+
let logger: {
|
|
165
158
|
"__#13@#originalUserLogger": {
|
|
166
159
|
assert(condition?: boolean, ...data: any[]): void;
|
|
167
160
|
assert(value: any, message?: string, ...optionalParams: any[]): void;
|
|
@@ -229,15 +222,13 @@ declare namespace _default {
|
|
|
229
222
|
}): void;
|
|
230
223
|
prettyObjects: boolean;
|
|
231
224
|
};
|
|
232
|
-
|
|
225
|
+
let meta: (keyValue: {
|
|
233
226
|
[key: string]: string;
|
|
234
227
|
} | string, value?: string | null) => void;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
export { Client as TestomatioClient };
|
|
240
|
-
export { STATUS };
|
|
228
|
+
let step: (message: string) => void;
|
|
229
|
+
let label: (key: string, value?: string | null) => void;
|
|
230
|
+
let linkTest: (...testIds: string[]) => void;
|
|
231
|
+
let linkJira: (...jiraIds: string[]) => void;
|
|
241
232
|
}
|
|
242
233
|
export default _default;
|
|
243
234
|
export type ArtifactFunction = typeof import("./reporter-functions.js").default.artifact;
|
|
@@ -246,4 +237,3 @@ export type LoggerService = typeof import("./services/index.js").services.logger
|
|
|
246
237
|
export type MetaFunction = typeof import("./reporter-functions.js").default.keyValue;
|
|
247
238
|
export type StepFunction = typeof import("./reporter-functions.js").default.step;
|
|
248
239
|
export type LabelFunction = typeof import("./reporter-functions.js").default.label;
|
|
249
|
-
import Client from './client.js';
|
package/lib/reporter.js
CHANGED
|
@@ -1,48 +1,13 @@
|
|
|
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
|
-
exports.linkJira = exports.linkTest = exports.label = exports.step = exports.meta = exports.logger = exports.log = exports.artifact =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const TestomatioConstants = __importStar(require("./constants.js"));
|
|
6
|
+
exports.linkJira = exports.linkTest = exports.label = exports.step = exports.meta = exports.logger = exports.log = exports.artifact = void 0;
|
|
7
|
+
// import TestomatClient from './client.js';
|
|
8
|
+
// import * as TRConstants from './constants.js';
|
|
43
9
|
const index_js_1 = require("./services/index.js");
|
|
44
10
|
const reporter_functions_js_1 = __importDefault(require("./reporter-functions.js"));
|
|
45
|
-
exports.STATUS = TestomatioConstants.STATUS;
|
|
46
11
|
exports.artifact = reporter_functions_js_1.default.artifact;
|
|
47
12
|
exports.log = reporter_functions_js_1.default.log;
|
|
48
13
|
exports.logger = index_js_1.services.logger;
|
|
@@ -72,6 +37,6 @@ module.exports = {
|
|
|
72
37
|
label: reporter_functions_js_1.default.label,
|
|
73
38
|
linkTest: reporter_functions_js_1.default.linkTest,
|
|
74
39
|
linkJira: reporter_functions_js_1.default.linkJira,
|
|
75
|
-
|
|
76
|
-
|
|
40
|
+
// TestomatClient,
|
|
41
|
+
// TRConstants,
|
|
77
42
|
};
|
package/lib/utils/utils.js
CHANGED
|
@@ -471,14 +471,10 @@ function transformEnvVarToBoolean(value) {
|
|
|
471
471
|
return Boolean(value);
|
|
472
472
|
}
|
|
473
473
|
function truncate(s, size = 255) {
|
|
474
|
-
if (s
|
|
475
|
-
return
|
|
476
|
-
}
|
|
477
|
-
const str = s.toString();
|
|
478
|
-
if (str.trim().length < size) {
|
|
479
|
-
return str;
|
|
474
|
+
if (s.toString().trim().length < size) {
|
|
475
|
+
return s.toString();
|
|
480
476
|
}
|
|
481
|
-
return `${
|
|
477
|
+
return `${s.toString().substring(0, size)}...`;
|
|
482
478
|
}
|
|
483
479
|
|
|
484
480
|
module.exports.getPackageVersion = getPackageVersion;
|
package/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.9-beta-bin-fix",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18"
|
|
7
7
|
},
|
|
8
|
-
"
|
|
9
|
-
"module": "src/reporter.js",
|
|
10
|
-
"types": "types/types.d.ts",
|
|
8
|
+
"typings": "typings/index.d.ts",
|
|
11
9
|
"repository": "git@github.com:testomatio/reporter.git",
|
|
12
10
|
"author": "Michael Bodnarchuk <davert@testomat.io>,Koushik Mohan <koushikmohan1996@gmail.com>",
|
|
13
11
|
"license": "MIT",
|
|
@@ -47,8 +45,7 @@
|
|
|
47
45
|
"bin",
|
|
48
46
|
"lib",
|
|
49
47
|
"src",
|
|
50
|
-
"testcafe"
|
|
51
|
-
"types"
|
|
48
|
+
"testcafe"
|
|
52
49
|
],
|
|
53
50
|
"scripts": {
|
|
54
51
|
"clear-exportdir": "rm -rf export/",
|
|
@@ -60,8 +57,7 @@
|
|
|
60
57
|
"test": "mocha 'tests/unit/**/*_test.js'",
|
|
61
58
|
"test:playwright": "mocha tests/adapter/playwright.test.js",
|
|
62
59
|
"test:codecept": "mocha tests/adapter/codecept.test.js tests/adapter/codecept_comprehensive.test.js tests/adapter/codecept_steps_sections.test.js",
|
|
63
|
-
"test:
|
|
64
|
-
"test:frameworks": "npm run test:playwright && npm run test:codecept && npm run test:vitest",
|
|
60
|
+
"test:frameworks": "npm run test:playwright && npm run test:codecept",
|
|
65
61
|
"test:all": "npm run test && npm run test:frameworks",
|
|
66
62
|
"test:adapters": "mocha tests/adapter/*.test.js",
|
|
67
63
|
"test:codecept:bug948": "mocha tests/adapter/codecept_aftersuite_failure.test.js",
|
|
@@ -106,7 +102,7 @@
|
|
|
106
102
|
"vitest": "^1.6.0"
|
|
107
103
|
},
|
|
108
104
|
"bin": {
|
|
109
|
-
"
|
|
105
|
+
"testomatio/reporter": "./lib/bin/cli.js",
|
|
110
106
|
"report-xml": "./lib/bin/reportXml.js",
|
|
111
107
|
"start-test-run": "./lib/bin/startTest.js",
|
|
112
108
|
"upload-artifacts": "./lib/bin/uploadArtifacts.js"
|
package/src/bin/cli.js
CHANGED
|
@@ -158,7 +158,7 @@ program
|
|
|
158
158
|
.option('--lang <lang>', 'Language used (python, ruby, java)')
|
|
159
159
|
.option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
|
|
160
160
|
.action(async (pattern, opts) => {
|
|
161
|
-
if (!pattern.endsWith('.xml')
|
|
161
|
+
if (!pattern.endsWith('.xml')) {
|
|
162
162
|
pattern += '.xml';
|
|
163
163
|
}
|
|
164
164
|
let { javaTests, lang } = opts;
|
package/src/bin/reportXml.js
CHANGED
|
@@ -23,7 +23,7 @@ program
|
|
|
23
23
|
.option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
|
|
24
24
|
.option('--env-file <envfile>', 'Load environment variables from env file')
|
|
25
25
|
.action(async (pattern, opts) => {
|
|
26
|
-
if (!pattern.endsWith('.xml')
|
|
26
|
+
if (!pattern.endsWith('.xml')) {
|
|
27
27
|
pattern += '.xml';
|
|
28
28
|
}
|
|
29
29
|
let { javaTests, lang } = opts;
|
|
@@ -34,10 +34,7 @@ program
|
|
|
34
34
|
}
|
|
35
35
|
lang = lang?.toLowerCase();
|
|
36
36
|
if (javaTests === true || (lang === 'java' && !javaTests)) javaTests = 'src/test/java';
|
|
37
|
-
const runReader = new XmlReader({
|
|
38
|
-
javaTests,
|
|
39
|
-
lang,
|
|
40
|
-
});
|
|
37
|
+
const runReader = new XmlReader({ javaTests, lang });
|
|
41
38
|
const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
42
39
|
if (!files.length) {
|
|
43
40
|
console.log(APP_PREFIX, `Report can't be created. No XML files found π₯`);
|
package/src/client.js
CHANGED
|
@@ -10,21 +10,11 @@ import { glob } from 'glob';
|
|
|
10
10
|
import path, { sep } from 'path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { S3Uploader } from './uploader.js';
|
|
13
|
-
import {
|
|
14
|
-
formatStep,
|
|
15
|
-
truncate,
|
|
16
|
-
readLatestRunId,
|
|
17
|
-
storeRunId,
|
|
18
|
-
validateSuiteId,
|
|
19
|
-
transformEnvVarToBoolean
|
|
20
|
-
} from './utils/utils.js';
|
|
13
|
+
import { formatStep, truncate, readLatestRunId, storeRunId, validateSuiteId } from './utils/utils.js';
|
|
21
14
|
import { filesize as prettyBytes } from 'filesize';
|
|
22
|
-
import { stripVTControlCharacters } from 'util';
|
|
23
15
|
|
|
24
16
|
const debug = createDebugMessages('@testomatio/reporter:client');
|
|
25
17
|
|
|
26
|
-
const stripColors = stripVTControlCharacters || ((str) => str?.replace(/\x1b\[[0-9;]*m/g, '') || '');
|
|
27
|
-
|
|
28
18
|
// removed __dirname usage, because:
|
|
29
19
|
// 1. replaced with ESM syntax (import.meta.url), but it throws an error on tsc compilation;
|
|
30
20
|
// 2. got error "__dirname already defined" in compiles js code (cjs dir)
|
|
@@ -149,6 +139,19 @@ class Client {
|
|
|
149
139
|
* @returns {Promise<PipeResult[]>}
|
|
150
140
|
*/
|
|
151
141
|
async addTestRun(status, testData) {
|
|
142
|
+
if (!this.pipes || !this.pipes.length)
|
|
143
|
+
this.pipes = await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
144
|
+
|
|
145
|
+
// all pipes disabled, skipping
|
|
146
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
147
|
+
|
|
148
|
+
if (isTestShouldBeExculedFromReport(testData)) return [];
|
|
149
|
+
|
|
150
|
+
if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
|
|
151
|
+
debug('Skipping test from report', testData?.title);
|
|
152
|
+
return []; // do not log skipped tests
|
|
153
|
+
}
|
|
154
|
+
|
|
152
155
|
if (!testData)
|
|
153
156
|
testData = {
|
|
154
157
|
title: 'Unknown test',
|
|
@@ -166,23 +169,15 @@ class Client {
|
|
|
166
169
|
const {
|
|
167
170
|
rid,
|
|
168
171
|
error = null,
|
|
169
|
-
steps: originalSteps,
|
|
170
|
-
title,
|
|
171
|
-
suite_title,
|
|
172
|
-
} = testData;
|
|
173
|
-
let steps = originalSteps;
|
|
174
|
-
|
|
175
|
-
const uploadedFiles = [];
|
|
176
|
-
const stackArtifactsEnabled = transformEnvVarToBoolean(process.env.TESTOMATIO_STACK_ARTIFACTS);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const {
|
|
180
172
|
time = 0,
|
|
181
173
|
example = null,
|
|
182
174
|
files = [],
|
|
183
175
|
filesBuffers = [],
|
|
176
|
+
steps,
|
|
184
177
|
code = null,
|
|
178
|
+
title,
|
|
185
179
|
file,
|
|
180
|
+
suite_title,
|
|
186
181
|
suite_id,
|
|
187
182
|
test_id,
|
|
188
183
|
timestamp,
|
|
@@ -193,6 +188,7 @@ class Client {
|
|
|
193
188
|
} = testData;
|
|
194
189
|
let { message = '', meta = {} } = testData;
|
|
195
190
|
|
|
191
|
+
// stringify meta values and limit keys and values length to 255
|
|
196
192
|
meta = Object.entries(meta)
|
|
197
193
|
.filter(([, value]) => value !== null && value !== undefined)
|
|
198
194
|
.reduce((acc, [key, value]) => {
|
|
@@ -200,6 +196,7 @@ class Client {
|
|
|
200
196
|
return acc;
|
|
201
197
|
}, {});
|
|
202
198
|
|
|
199
|
+
// Get links from storage using the test context
|
|
203
200
|
const testContext = suite_title ? `${suite_title} ${title}` : title;
|
|
204
201
|
|
|
205
202
|
let errorFormatted = '';
|
|
@@ -208,39 +205,14 @@ class Client {
|
|
|
208
205
|
message = error?.message;
|
|
209
206
|
}
|
|
210
207
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (stackArtifactsEnabled && fullLogs?.trim()?.length > 0) {
|
|
214
|
-
uploadedFiles.push(
|
|
215
|
-
this.uploader.uploadFileAsBuffer(
|
|
216
|
-
Buffer.from(stripColors(fullLogs), 'utf8'),
|
|
217
|
-
[this.runId, rid, `logs_${+new Date}.log`]
|
|
218
|
-
)
|
|
219
|
-
);
|
|
220
|
-
fullLogs = '';
|
|
221
|
-
steps = null;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (!this.pipes || !this.pipes.length)
|
|
226
|
-
this.pipes = await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
227
|
-
|
|
228
|
-
if (!this.pipes?.filter(p => p.isEnabled).length) {
|
|
229
|
-
if (uploadedFiles.length > 0) {
|
|
230
|
-
await Promise.all(uploadedFiles);
|
|
231
|
-
}
|
|
232
|
-
return [];
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (isTestShouldBeExculedFromReport(testData)) return [];
|
|
236
|
-
|
|
237
|
-
if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
|
|
238
|
-
debug('Skipping test from report', testData?.title);
|
|
239
|
-
return [];
|
|
240
|
-
}
|
|
208
|
+
// Attach logs
|
|
209
|
+
const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
241
210
|
|
|
211
|
+
// add artifacts
|
|
242
212
|
if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
|
|
243
213
|
|
|
214
|
+
const uploadedFiles = [];
|
|
215
|
+
|
|
244
216
|
for (let f of files) {
|
|
245
217
|
if (!f) continue; // f === null
|
|
246
218
|
if (typeof f === 'object') {
|
|
@@ -336,7 +308,7 @@ class Client {
|
|
|
336
308
|
const uploadedArtifacts = this.uploader.successfulUploads.map(file => ({
|
|
337
309
|
relativePath: file.path.replace(process.cwd(), ''),
|
|
338
310
|
link: file.link,
|
|
339
|
-
sizePretty:
|
|
311
|
+
sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
|
|
340
312
|
}));
|
|
341
313
|
|
|
342
314
|
uploadedArtifacts.forEach(upload => {
|
|
@@ -358,7 +330,7 @@ class Client {
|
|
|
358
330
|
);
|
|
359
331
|
const failedUploads = this.uploader.failedUploads.map(file => ({
|
|
360
332
|
relativePath: file.path.replace(process.cwd(), ''),
|
|
361
|
-
sizePretty:
|
|
333
|
+
sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
|
|
362
334
|
}));
|
|
363
335
|
|
|
364
336
|
const pathPadding = Math.max(...failedUploads.map(upload => upload.relativePath.length)) + 1;
|
|
@@ -3,50 +3,18 @@ import Adapter from './adapter.js';
|
|
|
3
3
|
|
|
4
4
|
class CSharpAdapter extends Adapter {
|
|
5
5
|
formatTest(t) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (exampleMatch) {
|
|
10
|
-
// Extract parameters as object with numeric keys for API
|
|
11
|
-
const params = exampleMatch[1].split(',').map(param => param.trim());
|
|
12
|
-
t.example = {};
|
|
13
|
-
params.forEach((param, index) => {
|
|
14
|
-
t.example[index] = param;
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Remove parameters from title to avoid duplicates in Test Suite
|
|
20
|
-
// The example field will be used for grouping on import
|
|
21
|
-
t.title = t.title.replace(/\(.*?\)/, '').trim();
|
|
22
|
-
|
|
6
|
+
const title = t.title.replace(/\(.*?\)/, '').trim();
|
|
7
|
+
const example = t.title.match(/\((.*?)\)/);
|
|
8
|
+
if (example) t.example = { ...example[1].split(',') };
|
|
23
9
|
const suite = t.suite_title.split('.');
|
|
24
10
|
t.suite_title = suite.pop();
|
|
25
11
|
t.file = namespaceToFileName(t.file);
|
|
12
|
+
t.title = title.trim();
|
|
26
13
|
return t;
|
|
27
14
|
}
|
|
28
15
|
|
|
29
16
|
getFilePath(t) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// Normalize path separators for cross-platform compatibility
|
|
33
|
-
let filePath = t.file.replace(/\\/g, '/');
|
|
34
|
-
|
|
35
|
-
// If file already has .cs extension, use it directly
|
|
36
|
-
if (filePath.endsWith('.cs')) {
|
|
37
|
-
// Make relative path if it's absolute
|
|
38
|
-
if (path.isAbsolute(filePath)) {
|
|
39
|
-
// Try to find project-relative path
|
|
40
|
-
const cwd = process.cwd().replace(/\\/g, '/');
|
|
41
|
-
if (filePath.startsWith(cwd)) {
|
|
42
|
-
filePath = path.relative(cwd, filePath).replace(/\\/g, '/');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return filePath;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Convert namespace path to file path
|
|
49
|
-
const fileName = namespaceToFileName(filePath);
|
|
17
|
+
const fileName = namespaceToFileName(t.file);
|
|
50
18
|
return fileName;
|
|
51
19
|
}
|
|
52
20
|
}
|
|
@@ -54,14 +22,7 @@ class CSharpAdapter extends Adapter {
|
|
|
54
22
|
export default CSharpAdapter;
|
|
55
23
|
|
|
56
24
|
function namespaceToFileName(fileName) {
|
|
57
|
-
if (!fileName) return '';
|
|
58
|
-
|
|
59
|
-
// If already a .cs file path, clean it up
|
|
60
|
-
if (fileName.endsWith('.cs')) {
|
|
61
|
-
return fileName.replace(/\\/g, '/');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
25
|
const fileParts = fileName.split('.');
|
|
65
26
|
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
66
|
-
return `${fileParts.join(
|
|
27
|
+
return `${fileParts.join(path.sep)}.cs`;
|
|
67
28
|
}
|