@testomatio/reporter 2.0.1-beta-2-ignore-xml → 2.0.1-beta.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/lib/adapter/codecept.js +0 -2
- package/lib/adapter/cypress-plugin/index.js +0 -2
- package/lib/adapter/mocha.js +0 -1
- package/lib/adapter/nightwatch.d.ts +4 -0
- package/lib/adapter/nightwatch.js +80 -0
- package/lib/adapter/webdriver.d.ts +1 -1
- package/lib/adapter/webdriver.js +18 -8
- package/lib/bin/cli.js +73 -8
- package/lib/bin/reportXml.js +4 -2
- package/lib/bin/startTest.js +3 -2
- package/lib/bin/uploadArtifacts.js +5 -4
- package/lib/client.js +18 -9
- package/lib/config.js +2 -2
- package/lib/data-storage.d.ts +1 -1
- package/lib/data-storage.js +17 -7
- package/lib/junit-adapter/csharp.d.ts +1 -0
- package/lib/junit-adapter/csharp.js +11 -1
- package/lib/pipe/bitbucket.d.ts +2 -0
- package/lib/pipe/bitbucket.js +38 -26
- package/lib/pipe/debug.js +17 -3
- package/lib/pipe/github.d.ts +2 -2
- package/lib/pipe/github.js +35 -3
- package/lib/pipe/gitlab.d.ts +2 -0
- package/lib/pipe/gitlab.js +27 -9
- package/lib/pipe/html.d.ts +1 -0
- package/lib/pipe/html.js +1 -3
- package/lib/pipe/index.js +17 -7
- package/lib/pipe/testomatio.d.ts +3 -2
- package/lib/pipe/testomatio.js +85 -75
- package/lib/replay.d.ts +31 -0
- package/lib/replay.js +237 -0
- package/lib/reporter.d.ts +12 -12
- package/lib/services/artifacts.d.ts +1 -1
- package/lib/services/key-values.d.ts +1 -1
- package/lib/services/logger.d.ts +1 -1
- package/lib/services/logger.js +1 -2
- package/lib/template/testomatio.hbs +443 -68
- package/lib/uploader.js +10 -6
- package/lib/utils/utils.d.ts +3 -1
- package/lib/utils/utils.js +54 -21
- package/lib/xmlReader.js +54 -19
- package/package.json +8 -9
- package/src/adapter/codecept.js +0 -2
- package/src/adapter/cypress-plugin/index.js +0 -2
- package/src/adapter/mocha.js +0 -1
- package/src/adapter/nightwatch.js +88 -0
- package/src/adapter/webdriver.js +2 -2
- package/src/bin/cli.js +70 -2
- package/src/bin/reportXml.js +4 -1
- package/src/bin/startTest.js +2 -1
- package/src/bin/uploadArtifacts.js +2 -1
- package/src/client.js +1 -2
- package/src/config.js +2 -2
- package/src/junit-adapter/csharp.js +13 -1
- package/src/pipe/bitbucket.js +22 -24
- package/src/pipe/debug.js +18 -3
- package/src/pipe/github.js +1 -2
- package/src/pipe/gitlab.js +27 -9
- package/src/pipe/html.js +3 -4
- package/src/pipe/testomatio.js +106 -105
- package/src/replay.js +245 -0
- package/src/services/logger.js +1 -2
- package/src/template/testomatio.hbs +443 -68
- package/src/uploader.js +11 -6
- package/src/utils/utils.js +31 -12
- package/src/xmlReader.js +69 -17
package/lib/replay.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
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.Replay = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const client_js_1 = __importDefault(require("./client.js"));
|
|
11
|
+
const constants_js_1 = require("./constants.js");
|
|
12
|
+
const config_js_1 = require("./config.js");
|
|
13
|
+
class Replay {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.apiKey = options.apiKey || config_js_1.config.TESTOMATIO || undefined;
|
|
16
|
+
this.dryRun = options.dryRun || false;
|
|
17
|
+
this.onProgress = options.onProgress || (() => { });
|
|
18
|
+
this.onLog = options.onLog || console.log;
|
|
19
|
+
this.onError = options.onError || console.error;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get the default debug file path
|
|
23
|
+
* @returns {string} Path to the latest debug file
|
|
24
|
+
*/
|
|
25
|
+
getDefaultDebugFile() {
|
|
26
|
+
return path_1.default.join(os_1.default.tmpdir(), 'testomatio.debug.latest.json');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parse a debug file and extract test data
|
|
30
|
+
* @param {string} debugFile - Path to the debug file
|
|
31
|
+
* @returns {Object} Parsed debug data
|
|
32
|
+
*/
|
|
33
|
+
parseDebugFile(debugFile) {
|
|
34
|
+
if (!fs_1.default.existsSync(debugFile)) {
|
|
35
|
+
throw new Error(`Debug file not found: ${debugFile}`);
|
|
36
|
+
}
|
|
37
|
+
const fileContent = fs_1.default.readFileSync(debugFile, 'utf-8');
|
|
38
|
+
const lines = fileContent.trim().split('\n').filter(line => line.trim() !== '');
|
|
39
|
+
if (lines.length === 0) {
|
|
40
|
+
throw new Error('Debug file is empty');
|
|
41
|
+
}
|
|
42
|
+
let runParams = {};
|
|
43
|
+
let finishParams = {};
|
|
44
|
+
let parseErrors = 0;
|
|
45
|
+
const testsMap = new Map(); // Use Map to deduplicate by rid
|
|
46
|
+
const testsWithoutRid = []; // For tests without rid (backward compatibility)
|
|
47
|
+
const envVars = {};
|
|
48
|
+
// Parse debug file line by line
|
|
49
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
50
|
+
try {
|
|
51
|
+
const logEntry = JSON.parse(line);
|
|
52
|
+
if (logEntry.data === 'variables' && logEntry.testomatioEnvVars) {
|
|
53
|
+
Object.assign(envVars, logEntry.testomatioEnvVars);
|
|
54
|
+
}
|
|
55
|
+
else if (logEntry.action === 'createRun') {
|
|
56
|
+
runParams = logEntry.params || {};
|
|
57
|
+
}
|
|
58
|
+
else if (logEntry.action === 'addTestsBatch' && logEntry.tests) {
|
|
59
|
+
// Process each test in the batch
|
|
60
|
+
for (const test of logEntry.tests) {
|
|
61
|
+
if (test.rid) {
|
|
62
|
+
// Handle tests with rid (deduplicate)
|
|
63
|
+
const existingTest = testsMap.get(test.rid);
|
|
64
|
+
if (existingTest) {
|
|
65
|
+
// Merge test data - prioritize non-null/non-empty values
|
|
66
|
+
const mergedTest = { ...existingTest };
|
|
67
|
+
Object.keys(test).forEach(key => {
|
|
68
|
+
if (test[key] !== null && test[key] !== undefined) {
|
|
69
|
+
if (key === 'files' && Array.isArray(test[key]) && test[key].length > 0) {
|
|
70
|
+
// Merge files arrays
|
|
71
|
+
mergedTest.files = [...(existingTest.files || []), ...test[key]];
|
|
72
|
+
}
|
|
73
|
+
else if (key === 'artifacts' && Array.isArray(test[key]) && test[key].length > 0) {
|
|
74
|
+
// Merge artifacts arrays
|
|
75
|
+
mergedTest.artifacts = [...(existingTest.artifacts || []), ...test[key]];
|
|
76
|
+
}
|
|
77
|
+
else if (existingTest[key] === null || existingTest[key] === undefined ||
|
|
78
|
+
(Array.isArray(existingTest[key]) && existingTest[key].length === 0)) {
|
|
79
|
+
// Use new value if existing is null/undefined/empty array
|
|
80
|
+
mergedTest[key] = test[key];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
testsMap.set(test.rid, mergedTest);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
testsMap.set(test.rid, { ...test });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Handle tests without rid (no deduplication)
|
|
92
|
+
testsWithoutRid.push({ ...test });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (logEntry.action === 'addTest' && logEntry.testId) {
|
|
97
|
+
const test = logEntry.testId;
|
|
98
|
+
if (test.rid) {
|
|
99
|
+
// Handle tests with rid (deduplicate)
|
|
100
|
+
const existingTest = testsMap.get(test.rid);
|
|
101
|
+
if (existingTest) {
|
|
102
|
+
// Merge with existing test
|
|
103
|
+
const mergedTest = { ...existingTest, ...test };
|
|
104
|
+
testsMap.set(test.rid, mergedTest);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
testsMap.set(test.rid, { ...test });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Handle tests without rid (no deduplication)
|
|
112
|
+
testsWithoutRid.push({ ...test });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (logEntry.actions === 'finishRun') {
|
|
116
|
+
finishParams = logEntry.params || {};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
parseErrors++;
|
|
121
|
+
if (parseErrors <= 3) {
|
|
122
|
+
// Only show first 3 parse errors
|
|
123
|
+
this.onError(`Failed to parse line ${lineIndex + 1}: ${line.substring(0, 100)}...`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (parseErrors > 3) {
|
|
128
|
+
this.onError(`${parseErrors - 3} more parse errors occurred`);
|
|
129
|
+
}
|
|
130
|
+
// Combine tests with rid and tests without rid
|
|
131
|
+
const allTests = [...Array.from(testsMap.values()), ...testsWithoutRid];
|
|
132
|
+
return {
|
|
133
|
+
runParams,
|
|
134
|
+
finishParams,
|
|
135
|
+
tests: allTests,
|
|
136
|
+
envVars,
|
|
137
|
+
parseErrors,
|
|
138
|
+
totalLines: lines.length
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Restore environment variables from debug data
|
|
143
|
+
* @param {Object} envVars - Environment variables to restore
|
|
144
|
+
*/
|
|
145
|
+
restoreEnvironmentVariables(envVars) {
|
|
146
|
+
// Only restore env vars that aren't already set (don't override current values)
|
|
147
|
+
Object.keys(envVars).forEach(key => {
|
|
148
|
+
if (process.env[key] === undefined || process.env[key] === '') {
|
|
149
|
+
process.env[key] = envVars[key];
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Replay test data to Testomat.io
|
|
155
|
+
* @param {string} debugFile - Path to debug file (optional, uses default if not provided)
|
|
156
|
+
* @returns {Promise<Object>} Replay results
|
|
157
|
+
*/
|
|
158
|
+
async replay(debugFile) {
|
|
159
|
+
if (!debugFile) {
|
|
160
|
+
debugFile = this.getDefaultDebugFile();
|
|
161
|
+
}
|
|
162
|
+
if (!this.apiKey) {
|
|
163
|
+
throw new Error('TESTOMATIO API key not found. Set TESTOMATIO environment variable.');
|
|
164
|
+
}
|
|
165
|
+
this.onLog(`Replaying data from debug file: ${debugFile}`);
|
|
166
|
+
// Parse the debug file
|
|
167
|
+
const debugData = this.parseDebugFile(debugFile);
|
|
168
|
+
const { runParams, finishParams, tests, envVars } = debugData;
|
|
169
|
+
this.onLog(`Found ${tests.length} tests to replay`);
|
|
170
|
+
if (tests.length === 0) {
|
|
171
|
+
throw new Error('No test data found in debug file');
|
|
172
|
+
}
|
|
173
|
+
// Restore environment variables
|
|
174
|
+
this.restoreEnvironmentVariables(envVars);
|
|
175
|
+
if (this.dryRun) {
|
|
176
|
+
return {
|
|
177
|
+
success: true,
|
|
178
|
+
testsCount: tests.length,
|
|
179
|
+
runParams,
|
|
180
|
+
finishParams,
|
|
181
|
+
envVars,
|
|
182
|
+
dryRun: true
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// Create client and restore the run
|
|
186
|
+
const client = new client_js_1.default({
|
|
187
|
+
apiKey: this.apiKey,
|
|
188
|
+
isBatchEnabled: true,
|
|
189
|
+
...runParams,
|
|
190
|
+
});
|
|
191
|
+
this.onLog('Publishing to run...');
|
|
192
|
+
await client.createRun(runParams);
|
|
193
|
+
// Send each test result
|
|
194
|
+
let successCount = 0;
|
|
195
|
+
let failureCount = 0;
|
|
196
|
+
for (const [index, test] of tests.entries()) {
|
|
197
|
+
try {
|
|
198
|
+
await client.addTestRun(test.status, test);
|
|
199
|
+
successCount++;
|
|
200
|
+
this.onProgress({
|
|
201
|
+
current: index + 1,
|
|
202
|
+
total: tests.length,
|
|
203
|
+
test,
|
|
204
|
+
success: true
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
failureCount++;
|
|
209
|
+
this.onError(`Failed to send test ${index + 1}: ${err.message}`);
|
|
210
|
+
this.onProgress({
|
|
211
|
+
current: index + 1,
|
|
212
|
+
total: tests.length,
|
|
213
|
+
test,
|
|
214
|
+
success: false,
|
|
215
|
+
error: err.message
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
await client.updateRunStatus(finishParams.status || constants_js_1.STATUS.FINISHED, finishParams.parallel || false);
|
|
220
|
+
const result = {
|
|
221
|
+
success: true,
|
|
222
|
+
testsCount: tests.length,
|
|
223
|
+
successCount,
|
|
224
|
+
failureCount,
|
|
225
|
+
runParams,
|
|
226
|
+
finishParams,
|
|
227
|
+
envVars,
|
|
228
|
+
runId: client.runId
|
|
229
|
+
};
|
|
230
|
+
this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
exports.Replay = Replay;
|
|
235
|
+
module.exports = Replay;
|
|
236
|
+
|
|
237
|
+
module.exports.Replay = Replay;
|
package/lib/reporter.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type log = typeof import("./reporter-functions.js");
|
|
|
8
8
|
export const log: (...args: any[]) => void;
|
|
9
9
|
export type logger = typeof import("./services/index.js");
|
|
10
10
|
export const logger: {
|
|
11
|
-
"__#
|
|
11
|
+
"__#13@#originalUserLogger": {
|
|
12
12
|
assert(condition?: boolean, ...data: any[]): void;
|
|
13
13
|
assert(value: any, message?: string, ...optionalParams: any[]): void;
|
|
14
14
|
clear(): void;
|
|
@@ -53,13 +53,13 @@ export const logger: {
|
|
|
53
53
|
profile(label?: string): void;
|
|
54
54
|
profileEnd(label?: string): void;
|
|
55
55
|
};
|
|
56
|
-
"__#
|
|
56
|
+
"__#13@#userLoggerWithOverridenMethods": any;
|
|
57
57
|
logLevel: string;
|
|
58
58
|
step(strings: any, ...values: any[]): void;
|
|
59
59
|
getLogs(context: string): string[];
|
|
60
|
-
"__#
|
|
60
|
+
"__#13@#stringifyLogs"(...args: any[]): string;
|
|
61
61
|
_templateLiteralLog(strings: any, ...args: any[]): void;
|
|
62
|
-
"__#
|
|
62
|
+
"__#13@#logWrapper"(argsArray: any, level: any): void;
|
|
63
63
|
assert(...args: any[]): void;
|
|
64
64
|
debug(...args: any[]): void;
|
|
65
65
|
error(...args: any[]): void;
|
|
@@ -83,7 +83,7 @@ export type step = typeof import("./reporter-functions.js");
|
|
|
83
83
|
export const step: (message: string) => void;
|
|
84
84
|
declare namespace _default {
|
|
85
85
|
let testomatioLogger: {
|
|
86
|
-
"__#
|
|
86
|
+
"__#13@#originalUserLogger": {
|
|
87
87
|
assert(condition?: boolean, ...data: any[]): void;
|
|
88
88
|
assert(value: any, message?: string, ...optionalParams: any[]): void;
|
|
89
89
|
clear(): void;
|
|
@@ -128,13 +128,13 @@ declare namespace _default {
|
|
|
128
128
|
profile(label?: string): void;
|
|
129
129
|
profileEnd(label?: string): void;
|
|
130
130
|
};
|
|
131
|
-
"__#
|
|
131
|
+
"__#13@#userLoggerWithOverridenMethods": any;
|
|
132
132
|
logLevel: string;
|
|
133
133
|
step(strings: any, ...values: any[]): void;
|
|
134
134
|
getLogs(context: string): string[];
|
|
135
|
-
"__#
|
|
135
|
+
"__#13@#stringifyLogs"(...args: any[]): string;
|
|
136
136
|
_templateLiteralLog(strings: any, ...args: any[]): void;
|
|
137
|
-
"__#
|
|
137
|
+
"__#13@#logWrapper"(argsArray: any, level: any): void;
|
|
138
138
|
assert(...args: any[]): void;
|
|
139
139
|
debug(...args: any[]): void;
|
|
140
140
|
error(...args: any[]): void;
|
|
@@ -157,7 +157,7 @@ declare namespace _default {
|
|
|
157
157
|
}, context?: any) => void;
|
|
158
158
|
let log: (...args: any[]) => void;
|
|
159
159
|
let logger: {
|
|
160
|
-
"__#
|
|
160
|
+
"__#13@#originalUserLogger": {
|
|
161
161
|
assert(condition?: boolean, ...data: any[]): void;
|
|
162
162
|
assert(value: any, message?: string, ...optionalParams: any[]): void;
|
|
163
163
|
clear(): void;
|
|
@@ -202,13 +202,13 @@ declare namespace _default {
|
|
|
202
202
|
profile(label?: string): void;
|
|
203
203
|
profileEnd(label?: string): void;
|
|
204
204
|
};
|
|
205
|
-
"__#
|
|
205
|
+
"__#13@#userLoggerWithOverridenMethods": any;
|
|
206
206
|
logLevel: string;
|
|
207
207
|
step(strings: any, ...values: any[]): void;
|
|
208
208
|
getLogs(context: string): string[];
|
|
209
|
-
"__#
|
|
209
|
+
"__#13@#stringifyLogs"(...args: any[]): string;
|
|
210
210
|
_templateLiteralLog(strings: any, ...args: any[]): void;
|
|
211
|
-
"__#
|
|
211
|
+
"__#13@#logWrapper"(argsArray: any, level: any): void;
|
|
212
212
|
assert(...args: any[]): void;
|
|
213
213
|
debug(...args: any[]): void;
|
|
214
214
|
error(...args: any[]): void;
|
package/lib/services/logger.d.ts
CHANGED
package/lib/services/logger.js
CHANGED
|
@@ -90,7 +90,6 @@ class Logger {
|
|
|
90
90
|
}
|
|
91
91
|
else {
|
|
92
92
|
try {
|
|
93
|
-
// eslint-disable-next-line no-unused-expressions
|
|
94
93
|
this.prettyObjects ? logs.push(JSON.stringify(arg, null, 2)) : logs.push(JSON.stringify(arg));
|
|
95
94
|
}
|
|
96
95
|
catch (e) {
|
|
@@ -119,7 +118,7 @@ class Logger {
|
|
|
119
118
|
current +
|
|
120
119
|
// strings are splitted by args when use tagged template, thus we add arg after each string
|
|
121
120
|
// it looks like: `string1 arg1 string2 arg2 string3`
|
|
122
|
-
(args[index] !== undefined
|
|
121
|
+
(args[index] !== undefined
|
|
123
122
|
? typeof args[index] === 'string'
|
|
124
123
|
? args[index] // add arg as it is
|
|
125
124
|
: this.#stringifyLogs(args[index]) // stringify arg
|