@testomatio/reporter 2.1.0-beta.1-codeceptjs → 2.1.0-beta.3-filter-plan
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/webdriver.js +9 -3
- package/lib/bin/cli.js +7 -5
- 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 +8 -3
- package/lib/pipe/testomatio.js +7 -3
- package/lib/replay.js +12 -8
- package/lib/utils/utils.d.ts +6 -1
- package/lib/utils/utils.js +23 -2
- package/lib/xmlReader.js +3 -0
- package/package.json +1 -1
- package/src/adapter/webdriver.js +11 -3
- package/src/bin/cli.js +9 -6
- package/src/client.js +11 -3
- package/src/pipe/testomatio.js +8 -4
- package/src/replay.js +16 -10
- package/src/utils/utils.js +21 -2
- package/src/xmlReader.js +3 -0
package/lib/adapter/webdriver.js
CHANGED
|
@@ -48,8 +48,9 @@ class WebdriverReporter extends reporter_1.default {
|
|
|
48
48
|
options = Object.assign(options, { stdout: true });
|
|
49
49
|
this._addTestPromises = [];
|
|
50
50
|
this._isSynchronising = false;
|
|
51
|
-
//
|
|
52
|
-
|
|
51
|
+
// run is created by cli, if enabling the row below, it mat lead to multiple runs being created
|
|
52
|
+
// thus, need to check if process.env.runId is set and/or add more checks to avoid creating multiple runs
|
|
53
|
+
// this.client.createRun();
|
|
53
54
|
}
|
|
54
55
|
get isSynchronised() {
|
|
55
56
|
return this._isSynchronising === false;
|
|
@@ -68,7 +69,6 @@ class WebdriverReporter extends reporter_1.default {
|
|
|
68
69
|
}
|
|
69
70
|
onRunnerStart() {
|
|
70
71
|
// clear dir with artifacts/logs
|
|
71
|
-
//
|
|
72
72
|
utils_js_1.fileSystem.clearDir(constants_js_1.TESTOMAT_TMP_STORAGE_DIR);
|
|
73
73
|
}
|
|
74
74
|
onTestStart(test) {
|
|
@@ -153,3 +153,9 @@ function getTestLogs(fullTestTitle) {
|
|
|
153
153
|
return logs;
|
|
154
154
|
}
|
|
155
155
|
module.exports = WebdriverReporter;
|
|
156
|
+
/* INVESTIGATION RESULTS:
|
|
157
|
+
If you run tests in parallel, the WDIO creates a separate process for each parallel instance.
|
|
158
|
+
As a result, there is own WDIOReporter instance for each parallel process.
|
|
159
|
+
This means, its impossible to create or finish run, because can't understand if its was already created
|
|
160
|
+
in other process or not.
|
|
161
|
+
*/
|
package/lib/bin/cli.js
CHANGED
|
@@ -38,6 +38,7 @@ program
|
|
|
38
38
|
.command('start')
|
|
39
39
|
.description('Start a new run and return its ID')
|
|
40
40
|
.action(async () => {
|
|
41
|
+
(0, utils_js_1.cleanLatestRunId)();
|
|
41
42
|
console.log('Starting a new Run on Testomat.io...');
|
|
42
43
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config_js_1.config.TESTOMATIO;
|
|
43
44
|
const client = new client_js_1.default({ apiKey });
|
|
@@ -66,6 +67,7 @@ program
|
|
|
66
67
|
});
|
|
67
68
|
program
|
|
68
69
|
.command('run')
|
|
70
|
+
.alias('test')
|
|
69
71
|
.description('Run tests with the specified command')
|
|
70
72
|
.argument('<command>', 'Test runner command')
|
|
71
73
|
.option('--filter <filter>', 'Additional execution filter')
|
|
@@ -91,24 +93,24 @@ program
|
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
console.log(constants_js_1.APP_PREFIX, `🚀 Running`, picocolors_1.default.green(command));
|
|
94
|
-
const runTests = () => {
|
|
96
|
+
const runTests = async () => {
|
|
95
97
|
const testCmds = command.split(' ');
|
|
96
98
|
const cmd = (0, cross_spawn_1.spawn)(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
|
|
97
|
-
cmd.on('close', code => {
|
|
99
|
+
cmd.on('close', async (code) => {
|
|
98
100
|
const emoji = code === 0 ? '🟢' : '🔴';
|
|
99
101
|
console.log(constants_js_1.APP_PREFIX, emoji, `Runner exited with ${picocolors_1.default.bold(code)}`);
|
|
100
102
|
if (apiKey) {
|
|
101
103
|
const status = code === 0 ? 'passed' : 'failed';
|
|
102
|
-
client.updateRunStatus(status, true);
|
|
104
|
+
await client.updateRunStatus(status, true);
|
|
103
105
|
}
|
|
104
106
|
process.exit(code);
|
|
105
107
|
});
|
|
106
108
|
};
|
|
107
109
|
if (apiKey) {
|
|
108
|
-
client.createRun().then(runTests);
|
|
110
|
+
await client.createRun().then(runTests);
|
|
109
111
|
}
|
|
110
112
|
else {
|
|
111
|
-
runTests();
|
|
113
|
+
await runTests();
|
|
112
114
|
}
|
|
113
115
|
});
|
|
114
116
|
// program
|
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
|
@@ -68,7 +68,7 @@ class Client {
|
|
|
68
68
|
constructor(params = {}) {
|
|
69
69
|
this.paramsForPipesFactory = params;
|
|
70
70
|
this.pipeStore = {};
|
|
71
|
-
this.runId =
|
|
71
|
+
this.runId = '';
|
|
72
72
|
this.queue = Promise.resolve();
|
|
73
73
|
// @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
|
|
74
74
|
const pathToPackageJSON = path_1.default.join(__dirname, '../package.json');
|
|
@@ -158,6 +158,8 @@ class Client {
|
|
|
158
158
|
* @returns {Promise<PipeResult[]>}
|
|
159
159
|
*/
|
|
160
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);
|
|
161
163
|
// all pipes disabled, skipping
|
|
162
164
|
if (!this.pipes?.filter(p => p.isEnabled).length)
|
|
163
165
|
return [];
|
|
@@ -179,7 +181,7 @@ class Client {
|
|
|
179
181
|
/**
|
|
180
182
|
* @type {TestData}
|
|
181
183
|
*/
|
|
182
|
-
const { rid, error = null, time = 0, example = null, files = [], filesBuffers = [], steps, code = null, title, file, suite_title, suite_id, test_id, timestamp, manuallyAttachedArtifacts, labels, } = testData;
|
|
184
|
+
const { rid, error = null, time = 0, example = null, files = [], filesBuffers = [], steps, code = null, title, file, suite_title, suite_id, test_id, timestamp, manuallyAttachedArtifacts, labels, overwrite, } = testData;
|
|
183
185
|
let { message = '', meta = {} } = testData;
|
|
184
186
|
// stringify meta values and limit keys and values length to 255
|
|
185
187
|
meta = Object.entries(meta)
|
|
@@ -267,6 +269,7 @@ class Client {
|
|
|
267
269
|
artifacts,
|
|
268
270
|
meta,
|
|
269
271
|
labels,
|
|
272
|
+
overwrite,
|
|
270
273
|
...(rootSuiteId && { root_suite_id: rootSuiteId }),
|
|
271
274
|
};
|
|
272
275
|
// debug('Adding test run...', data);
|
|
@@ -291,7 +294,9 @@ class Client {
|
|
|
291
294
|
* @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
|
|
292
295
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
293
296
|
*/
|
|
294
|
-
updateRunStatus(status, isParallel = false) {
|
|
297
|
+
async updateRunStatus(status, isParallel = false) {
|
|
298
|
+
this.pipes ||= await (0, index_js_1.pipesFactory)(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
299
|
+
this.runId ||= (0, utils_js_1.readLatestRunId)();
|
|
295
300
|
debug('Updating run status...');
|
|
296
301
|
// all pipes disabled, skipping
|
|
297
302
|
if (!this.pipes?.filter(p => p.isEnabled).length)
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -113,7 +113,8 @@ class TestomatioPipe {
|
|
|
113
113
|
const resp = await this.client.request({
|
|
114
114
|
method: 'GET',
|
|
115
115
|
url: '/api/test_grep',
|
|
116
|
-
params: q
|
|
116
|
+
params: q.params,
|
|
117
|
+
responseType: q.responseType
|
|
117
118
|
});
|
|
118
119
|
if (Array.isArray(resp.data?.tests) && resp.data?.tests?.length > 0) {
|
|
119
120
|
(0, utils_js_1.foundedTestLog)(constants_js_1.APP_PREFIX, resp.data.tests);
|
|
@@ -331,11 +332,14 @@ class TestomatioPipe {
|
|
|
331
332
|
* Adds a test to the batch uploader (or reports a single test if batch uploading is disabled)
|
|
332
333
|
*/
|
|
333
334
|
addTest(data) {
|
|
334
|
-
this.isEnabled = this.apiKey ?? this.isEnabled;
|
|
335
|
+
this.isEnabled = !!(this.apiKey ?? this.isEnabled);
|
|
335
336
|
if (!this.isEnabled)
|
|
336
337
|
return;
|
|
337
|
-
|
|
338
|
+
this.runId = this.runId || process.env.runId || this.store.runId || (0, utils_js_1.readLatestRunId)();
|
|
339
|
+
if (!this.runId) {
|
|
340
|
+
console.warn(constants_js_1.APP_PREFIX, picocolors_1.default.red('Run ID is not set, skipping test reporting'));
|
|
338
341
|
return;
|
|
342
|
+
}
|
|
339
343
|
// add test ID + run ID
|
|
340
344
|
if (data.rid)
|
|
341
345
|
data.rid = `${this.runId}-${data.rid}`;
|
package/lib/replay.js
CHANGED
|
@@ -35,7 +35,10 @@ class Replay {
|
|
|
35
35
|
throw new Error(`Debug file not found: ${debugFile}`);
|
|
36
36
|
}
|
|
37
37
|
const fileContent = fs_1.default.readFileSync(debugFile, 'utf-8');
|
|
38
|
-
const lines = fileContent
|
|
38
|
+
const lines = fileContent
|
|
39
|
+
.trim()
|
|
40
|
+
.split('\n')
|
|
41
|
+
.filter(line => line.trim() !== '');
|
|
39
42
|
if (lines.length === 0) {
|
|
40
43
|
throw new Error('Debug file is empty');
|
|
41
44
|
}
|
|
@@ -79,7 +82,8 @@ class Replay {
|
|
|
79
82
|
// Merge artifacts arrays
|
|
80
83
|
mergedTest.artifacts = [...(existingTest.artifacts || []), ...test[key]];
|
|
81
84
|
}
|
|
82
|
-
else if (existingTest[key] === null ||
|
|
85
|
+
else if (existingTest[key] === null ||
|
|
86
|
+
existingTest[key] === undefined ||
|
|
83
87
|
(Array.isArray(existingTest[key]) && existingTest[key].length === 0)) {
|
|
84
88
|
// Use new value if existing is null/undefined/empty array
|
|
85
89
|
mergedTest[key] = test[key];
|
|
@@ -145,7 +149,7 @@ class Replay {
|
|
|
145
149
|
envVars,
|
|
146
150
|
parseErrors,
|
|
147
151
|
totalLines: lines.length,
|
|
148
|
-
runId
|
|
152
|
+
runId,
|
|
149
153
|
};
|
|
150
154
|
}
|
|
151
155
|
/**
|
|
@@ -190,7 +194,7 @@ class Replay {
|
|
|
190
194
|
finishParams,
|
|
191
195
|
envVars,
|
|
192
196
|
runId,
|
|
193
|
-
dryRun: true
|
|
197
|
+
dryRun: true,
|
|
194
198
|
};
|
|
195
199
|
}
|
|
196
200
|
// Create client and restore the run
|
|
@@ -213,13 +217,13 @@ class Replay {
|
|
|
213
217
|
let failureCount = 0;
|
|
214
218
|
for (const [index, test] of tests.entries()) {
|
|
215
219
|
try {
|
|
216
|
-
await client.addTestRun(test.status, test);
|
|
220
|
+
await client.addTestRun(test.status, { ...test, overwrite: true });
|
|
217
221
|
successCount++;
|
|
218
222
|
this.onProgress({
|
|
219
223
|
current: index + 1,
|
|
220
224
|
total: tests.length,
|
|
221
225
|
test,
|
|
222
|
-
success: true
|
|
226
|
+
success: true,
|
|
223
227
|
});
|
|
224
228
|
}
|
|
225
229
|
catch (err) {
|
|
@@ -230,7 +234,7 @@ class Replay {
|
|
|
230
234
|
total: tests.length,
|
|
231
235
|
test,
|
|
232
236
|
success: false,
|
|
233
|
-
error: err.message
|
|
237
|
+
error: err.message,
|
|
234
238
|
});
|
|
235
239
|
}
|
|
236
240
|
}
|
|
@@ -243,7 +247,7 @@ class Replay {
|
|
|
243
247
|
runParams,
|
|
244
248
|
finishParams,
|
|
245
249
|
envVars,
|
|
246
|
-
runId: runId || client.runId
|
|
250
|
+
runId: runId || client.runId,
|
|
247
251
|
};
|
|
248
252
|
this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
|
|
249
253
|
return result;
|
package/lib/utils/utils.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export function getPackageVersion(): any;
|
|
|
2
2
|
export const TEST_ID_REGEX: RegExp;
|
|
3
3
|
export const SUITE_ID_REGEX: RegExp;
|
|
4
4
|
export function ansiRegExp(): RegExp;
|
|
5
|
+
export function cleanLatestRunId(): void;
|
|
5
6
|
export function isSameTest(test: any, t: any): boolean;
|
|
6
7
|
export function fetchSourceCode(contents: any, opts?: {}): string;
|
|
7
8
|
export function fetchSourceCodeFromStackTrace(stack?: string): string;
|
|
@@ -29,7 +30,11 @@ export function isValidUrl(s: any): boolean;
|
|
|
29
30
|
* @returns {String|null} suiteId
|
|
30
31
|
*/
|
|
31
32
|
export function parseSuite(suiteTitle: string): string | null;
|
|
32
|
-
|
|
33
|
+
/**
|
|
34
|
+
*
|
|
35
|
+
* @returns {String|null} latest run ID
|
|
36
|
+
*/
|
|
37
|
+
export function readLatestRunId(): string | null;
|
|
33
38
|
/**
|
|
34
39
|
* Used to remove color codes
|
|
35
40
|
* @param {*} input
|
package/lib/utils/utils.js
CHANGED
|
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.validateSuiteId = exports.testRunnerHelper = exports.specificTestInfo = exports.parseSuite = exports.isValidUrl = exports.humanize = exports.getTestomatIdFromTestTitle = exports.getCurrentDateTime = exports.foundedTestLog = exports.fileSystem = exports.fetchFilesFromStackTrace = exports.fetchIdFromOutput = exports.fetchIdFromCode = exports.fetchSourceCodeFromStackTrace = exports.fetchSourceCode = exports.isSameTest = exports.ansiRegExp = exports.SUITE_ID_REGEX = exports.TEST_ID_REGEX = void 0;
|
|
40
40
|
exports.getPackageVersion = getPackageVersion;
|
|
41
|
+
exports.cleanLatestRunId = cleanLatestRunId;
|
|
41
42
|
exports.formatStep = formatStep;
|
|
42
43
|
exports.readLatestRunId = readLatestRunId;
|
|
43
44
|
exports.removeColorCodes = removeColorCodes;
|
|
@@ -391,6 +392,10 @@ function storeRunId(runId) {
|
|
|
391
392
|
const filePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.latest.run`);
|
|
392
393
|
fs_1.default.writeFileSync(filePath, runId);
|
|
393
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
*
|
|
397
|
+
* @returns {String|null} latest run ID
|
|
398
|
+
*/
|
|
394
399
|
function readLatestRunId() {
|
|
395
400
|
try {
|
|
396
401
|
const filePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.latest.run`);
|
|
@@ -398,13 +403,27 @@ function readLatestRunId() {
|
|
|
398
403
|
const diff = +new Date() - +stats.mtime;
|
|
399
404
|
const diffHours = diff / 1000 / 60 / 60;
|
|
400
405
|
if (diffHours > 1)
|
|
401
|
-
return;
|
|
402
|
-
return fs_1.default.readFileSync(filePath)?.toString()?.trim();
|
|
406
|
+
return null;
|
|
407
|
+
return fs_1.default.readFileSync(filePath)?.toString()?.trim() ?? null;
|
|
403
408
|
}
|
|
404
409
|
catch (e) {
|
|
410
|
+
console.warn('Could not read latest run ID from file: ', e);
|
|
405
411
|
return null;
|
|
406
412
|
}
|
|
407
413
|
}
|
|
414
|
+
function cleanLatestRunId() {
|
|
415
|
+
try {
|
|
416
|
+
const filePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.latest.run`);
|
|
417
|
+
const runId = readLatestRunId();
|
|
418
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
419
|
+
fs_1.default.unlinkSync(filePath);
|
|
420
|
+
}
|
|
421
|
+
debug(`Cleaned latest run ID (${runId}) file`, filePath);
|
|
422
|
+
}
|
|
423
|
+
catch (e) {
|
|
424
|
+
console.warn('Could not clean latest run ID file: ', e);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
408
427
|
function formatStep(step, shift = 0) {
|
|
409
428
|
const prefix = ' '.repeat(shift);
|
|
410
429
|
const lines = [];
|
|
@@ -427,6 +446,8 @@ function getPackageVersion() {
|
|
|
427
446
|
|
|
428
447
|
module.exports.getPackageVersion = getPackageVersion;
|
|
429
448
|
|
|
449
|
+
module.exports.cleanLatestRunId = cleanLatestRunId;
|
|
450
|
+
|
|
430
451
|
module.exports.formatStep = formatStep;
|
|
431
452
|
|
|
432
453
|
module.exports.readLatestRunId = readLatestRunId;
|
package/lib/xmlReader.js
CHANGED
|
@@ -186,6 +186,7 @@ class XmlReader {
|
|
|
186
186
|
if (test.file)
|
|
187
187
|
r.file = test.file;
|
|
188
188
|
r.create = true;
|
|
189
|
+
r.overwrite = true;
|
|
189
190
|
if (r.status === 'Passed')
|
|
190
191
|
r.status = constants_js_1.STATUS.PASSED;
|
|
191
192
|
if (r.status === 'Failed')
|
|
@@ -254,6 +255,7 @@ class XmlReader {
|
|
|
254
255
|
title,
|
|
255
256
|
suite_title,
|
|
256
257
|
run_time,
|
|
258
|
+
retry: false,
|
|
257
259
|
});
|
|
258
260
|
});
|
|
259
261
|
});
|
|
@@ -509,6 +511,7 @@ function reduceTestCases(prev, item) {
|
|
|
509
511
|
root_suite_id: TESTOMATIO_SUITE,
|
|
510
512
|
suite_title: suiteTitle,
|
|
511
513
|
files,
|
|
514
|
+
retry: false,
|
|
512
515
|
});
|
|
513
516
|
});
|
|
514
517
|
return prev;
|
package/package.json
CHANGED
package/src/adapter/webdriver.js
CHANGED
|
@@ -14,8 +14,10 @@ class WebdriverReporter extends WDIOReporter {
|
|
|
14
14
|
this._addTestPromises = [];
|
|
15
15
|
|
|
16
16
|
this._isSynchronising = false;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
|
|
18
|
+
// run is created by cli, if enabling the row below, it mat lead to multiple runs being created
|
|
19
|
+
// thus, need to check if process.env.runId is set and/or add more checks to avoid creating multiple runs
|
|
20
|
+
// this.client.createRun();
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
get isSynchronised() {
|
|
@@ -40,7 +42,6 @@ class WebdriverReporter extends WDIOReporter {
|
|
|
40
42
|
|
|
41
43
|
onRunnerStart() {
|
|
42
44
|
// clear dir with artifacts/logs
|
|
43
|
-
//
|
|
44
45
|
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -140,3 +141,10 @@ function getTestLogs(fullTestTitle) {
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
export default WebdriverReporter;
|
|
144
|
+
|
|
145
|
+
/* INVESTIGATION RESULTS:
|
|
146
|
+
If you run tests in parallel, the WDIO creates a separate process for each parallel instance.
|
|
147
|
+
As a result, there is own WDIOReporter instance for each parallel process.
|
|
148
|
+
This means, its impossible to create or finish run, because can't understand if its was already created
|
|
149
|
+
in other process or not.
|
|
150
|
+
*/
|
package/src/bin/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import createDebugMessages from 'debug';
|
|
|
7
7
|
import TestomatClient from '../client.js';
|
|
8
8
|
import XmlReader from '../xmlReader.js';
|
|
9
9
|
import { APP_PREFIX, STATUS } from '../constants.js';
|
|
10
|
-
import { getPackageVersion } from '../utils/utils.js';
|
|
10
|
+
import { cleanLatestRunId, getPackageVersion } from '../utils/utils.js';
|
|
11
11
|
import { config } from '../config.js';
|
|
12
12
|
import { readLatestRunId } from '../utils/utils.js';
|
|
13
13
|
import pc from 'picocolors';
|
|
@@ -36,6 +36,8 @@ program
|
|
|
36
36
|
.command('start')
|
|
37
37
|
.description('Start a new run and return its ID')
|
|
38
38
|
.action(async () => {
|
|
39
|
+
cleanLatestRunId();
|
|
40
|
+
|
|
39
41
|
console.log('Starting a new Run on Testomat.io...');
|
|
40
42
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
|
|
41
43
|
const client = new TestomatClient({ apiKey });
|
|
@@ -70,6 +72,7 @@ program
|
|
|
70
72
|
|
|
71
73
|
program
|
|
72
74
|
.command('run')
|
|
75
|
+
.alias('test')
|
|
73
76
|
.description('Run tests with the specified command')
|
|
74
77
|
.argument('<command>', 'Test runner command')
|
|
75
78
|
.option('--filter <filter>', 'Additional execution filter')
|
|
@@ -100,25 +103,25 @@ program
|
|
|
100
103
|
|
|
101
104
|
console.log(APP_PREFIX, `🚀 Running`, pc.green(command));
|
|
102
105
|
|
|
103
|
-
const runTests = () => {
|
|
106
|
+
const runTests = async () => {
|
|
104
107
|
const testCmds = command.split(' ');
|
|
105
108
|
const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
|
|
106
109
|
|
|
107
|
-
cmd.on('close', code => {
|
|
110
|
+
cmd.on('close', async code => {
|
|
108
111
|
const emoji = code === 0 ? '🟢' : '🔴';
|
|
109
112
|
console.log(APP_PREFIX, emoji, `Runner exited with ${pc.bold(code)}`);
|
|
110
113
|
if (apiKey) {
|
|
111
114
|
const status = code === 0 ? 'passed' : 'failed';
|
|
112
|
-
client.updateRunStatus(status, true);
|
|
115
|
+
await client.updateRunStatus(status, true);
|
|
113
116
|
}
|
|
114
117
|
process.exit(code);
|
|
115
118
|
});
|
|
116
119
|
};
|
|
117
120
|
|
|
118
121
|
if (apiKey) {
|
|
119
|
-
client.createRun().then(runTests);
|
|
122
|
+
await client.createRun().then(runTests);
|
|
120
123
|
} else {
|
|
121
|
-
runTests();
|
|
124
|
+
await runTests();
|
|
122
125
|
}
|
|
123
126
|
});
|
|
124
127
|
|
package/src/client.js
CHANGED
|
@@ -10,7 +10,7 @@ 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 { formatStep, storeRunId, validateSuiteId } from './utils/utils.js';
|
|
13
|
+
import { formatStep, readLatestRunId, storeRunId, validateSuiteId } from './utils/utils.js';
|
|
14
14
|
import { filesize as prettyBytes } from 'filesize';
|
|
15
15
|
|
|
16
16
|
const debug = createDebugMessages('@testomatio/reporter:client');
|
|
@@ -34,7 +34,7 @@ class Client {
|
|
|
34
34
|
constructor(params = {}) {
|
|
35
35
|
this.paramsForPipesFactory = params;
|
|
36
36
|
this.pipeStore = {};
|
|
37
|
-
this.runId =
|
|
37
|
+
this.runId = '';
|
|
38
38
|
this.queue = Promise.resolve();
|
|
39
39
|
|
|
40
40
|
// @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
|
|
@@ -139,6 +139,9 @@ class Client {
|
|
|
139
139
|
* @returns {Promise<PipeResult[]>}
|
|
140
140
|
*/
|
|
141
141
|
async addTestRun(status, testData) {
|
|
142
|
+
if (!this.pipes || !this.pipes.length)
|
|
143
|
+
this.pipes = await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
144
|
+
|
|
142
145
|
// all pipes disabled, skipping
|
|
143
146
|
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
144
147
|
|
|
@@ -180,6 +183,7 @@ class Client {
|
|
|
180
183
|
timestamp,
|
|
181
184
|
manuallyAttachedArtifacts,
|
|
182
185
|
labels,
|
|
186
|
+
overwrite,
|
|
183
187
|
} = testData;
|
|
184
188
|
let { message = '', meta = {} } = testData;
|
|
185
189
|
|
|
@@ -277,6 +281,7 @@ class Client {
|
|
|
277
281
|
artifacts,
|
|
278
282
|
meta,
|
|
279
283
|
labels,
|
|
284
|
+
overwrite,
|
|
280
285
|
...(rootSuiteId && { root_suite_id: rootSuiteId }),
|
|
281
286
|
};
|
|
282
287
|
|
|
@@ -308,7 +313,10 @@ class Client {
|
|
|
308
313
|
* @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
|
|
309
314
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
310
315
|
*/
|
|
311
|
-
updateRunStatus(status, isParallel = false) {
|
|
316
|
+
async updateRunStatus(status, isParallel = false) {
|
|
317
|
+
this.pipes ||= await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
|
|
318
|
+
this.runId ||= readLatestRunId();
|
|
319
|
+
|
|
312
320
|
debug('Updating run status...');
|
|
313
321
|
// all pipes disabled, skipping
|
|
314
322
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
package/src/pipe/testomatio.js
CHANGED
|
@@ -3,7 +3,7 @@ import pc from 'picocolors';
|
|
|
3
3
|
import { Gaxios } from 'gaxios';
|
|
4
4
|
import JsonCycle from 'json-cycle';
|
|
5
5
|
import { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } from '../constants.js';
|
|
6
|
-
import { isValidUrl, foundedTestLog } from '../utils/utils.js';
|
|
6
|
+
import { isValidUrl, foundedTestLog, readLatestRunId } from '../utils/utils.js';
|
|
7
7
|
import { parseFilterParams, generateFilterRequestParams, setS3Credentials } from '../utils/pipe_utils.js';
|
|
8
8
|
import { config } from '../config.js';
|
|
9
9
|
|
|
@@ -367,10 +367,14 @@ class TestomatioPipe {
|
|
|
367
367
|
* Adds a test to the batch uploader (or reports a single test if batch uploading is disabled)
|
|
368
368
|
*/
|
|
369
369
|
addTest(data) {
|
|
370
|
-
this.isEnabled = this.apiKey ?? this.isEnabled;
|
|
371
|
-
|
|
370
|
+
this.isEnabled = !!(this.apiKey ?? this.isEnabled);
|
|
372
371
|
if (!this.isEnabled) return;
|
|
373
|
-
|
|
372
|
+
|
|
373
|
+
this.runId = this.runId || process.env.runId || this.store.runId || readLatestRunId();
|
|
374
|
+
if (!this.runId) {
|
|
375
|
+
console.warn(APP_PREFIX, pc.red('Run ID is not set, skipping test reporting'));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
374
378
|
|
|
375
379
|
// add test ID + run ID
|
|
376
380
|
if (data.rid) data.rid = `${this.runId}-${data.rid}`;
|
package/src/replay.js
CHANGED
|
@@ -33,7 +33,10 @@ export class Replay {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const fileContent = fs.readFileSync(debugFile, 'utf-8');
|
|
36
|
-
const lines = fileContent
|
|
36
|
+
const lines = fileContent
|
|
37
|
+
.trim()
|
|
38
|
+
.split('\n')
|
|
39
|
+
.filter(line => line.trim() !== '');
|
|
37
40
|
|
|
38
41
|
if (lines.length === 0) {
|
|
39
42
|
throw new Error('Debug file is empty');
|
|
@@ -77,8 +80,11 @@ export class Replay {
|
|
|
77
80
|
} else if (key === 'artifacts' && Array.isArray(test[key]) && test[key].length > 0) {
|
|
78
81
|
// Merge artifacts arrays
|
|
79
82
|
mergedTest.artifacts = [...(existingTest.artifacts || []), ...test[key]];
|
|
80
|
-
} else if (
|
|
81
|
-
|
|
83
|
+
} else if (
|
|
84
|
+
existingTest[key] === null ||
|
|
85
|
+
existingTest[key] === undefined ||
|
|
86
|
+
(Array.isArray(existingTest[key]) && existingTest[key].length === 0)
|
|
87
|
+
) {
|
|
82
88
|
// Use new value if existing is null/undefined/empty array
|
|
83
89
|
mergedTest[key] = test[key];
|
|
84
90
|
}
|
|
@@ -139,7 +145,7 @@ export class Replay {
|
|
|
139
145
|
envVars,
|
|
140
146
|
parseErrors,
|
|
141
147
|
totalLines: lines.length,
|
|
142
|
-
runId
|
|
148
|
+
runId,
|
|
143
149
|
};
|
|
144
150
|
}
|
|
145
151
|
|
|
@@ -193,7 +199,7 @@ export class Replay {
|
|
|
193
199
|
finishParams,
|
|
194
200
|
envVars,
|
|
195
201
|
runId,
|
|
196
|
-
dryRun: true
|
|
202
|
+
dryRun: true,
|
|
197
203
|
};
|
|
198
204
|
}
|
|
199
205
|
|
|
@@ -219,13 +225,13 @@ export class Replay {
|
|
|
219
225
|
|
|
220
226
|
for (const [index, test] of tests.entries()) {
|
|
221
227
|
try {
|
|
222
|
-
await client.addTestRun(test.status, test);
|
|
228
|
+
await client.addTestRun(test.status, { ...test, overwrite: true });
|
|
223
229
|
successCount++;
|
|
224
230
|
this.onProgress({
|
|
225
231
|
current: index + 1,
|
|
226
232
|
total: tests.length,
|
|
227
233
|
test,
|
|
228
|
-
success: true
|
|
234
|
+
success: true,
|
|
229
235
|
});
|
|
230
236
|
} catch (err) {
|
|
231
237
|
failureCount++;
|
|
@@ -235,7 +241,7 @@ export class Replay {
|
|
|
235
241
|
total: tests.length,
|
|
236
242
|
test,
|
|
237
243
|
success: false,
|
|
238
|
-
error: err.message
|
|
244
|
+
error: err.message,
|
|
239
245
|
});
|
|
240
246
|
}
|
|
241
247
|
}
|
|
@@ -250,7 +256,7 @@ export class Replay {
|
|
|
250
256
|
runParams,
|
|
251
257
|
finishParams,
|
|
252
258
|
envVars,
|
|
253
|
-
runId: runId || client.runId
|
|
259
|
+
runId: runId || client.runId,
|
|
254
260
|
};
|
|
255
261
|
|
|
256
262
|
this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
|
|
@@ -259,4 +265,4 @@ export class Replay {
|
|
|
259
265
|
}
|
|
260
266
|
}
|
|
261
267
|
|
|
262
|
-
export default Replay;
|
|
268
|
+
export default Replay;
|
package/src/utils/utils.js
CHANGED
|
@@ -353,20 +353,38 @@ function storeRunId(runId) {
|
|
|
353
353
|
fs.writeFileSync(filePath, runId);
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
+
/**
|
|
357
|
+
*
|
|
358
|
+
* @returns {String|null} latest run ID
|
|
359
|
+
*/
|
|
356
360
|
function readLatestRunId() {
|
|
357
361
|
try {
|
|
358
362
|
const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
|
|
359
363
|
const stats = fs.statSync(filePath);
|
|
360
364
|
const diff = +new Date() - +stats.mtime;
|
|
361
365
|
const diffHours = diff / 1000 / 60 / 60;
|
|
362
|
-
if (diffHours > 1) return;
|
|
366
|
+
if (diffHours > 1) return null;
|
|
363
367
|
|
|
364
|
-
return fs.readFileSync(filePath)?.toString()?.trim();
|
|
368
|
+
return fs.readFileSync(filePath)?.toString()?.trim() ?? null;
|
|
365
369
|
} catch (e) {
|
|
370
|
+
console.warn('Could not read latest run ID from file: ', e);
|
|
366
371
|
return null;
|
|
367
372
|
}
|
|
368
373
|
}
|
|
369
374
|
|
|
375
|
+
function cleanLatestRunId() {
|
|
376
|
+
try {
|
|
377
|
+
const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
|
|
378
|
+
const runId = readLatestRunId();
|
|
379
|
+
if (fs.existsSync(filePath)) {
|
|
380
|
+
fs.unlinkSync(filePath);
|
|
381
|
+
}
|
|
382
|
+
debug(`Cleaned latest run ID (${runId}) file`, filePath);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
console.warn('Could not clean latest run ID file: ', e);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
370
388
|
function formatStep(step, shift = 0) {
|
|
371
389
|
const prefix = ' '.repeat(shift);
|
|
372
390
|
|
|
@@ -393,6 +411,7 @@ export function getPackageVersion() {
|
|
|
393
411
|
|
|
394
412
|
export {
|
|
395
413
|
ansiRegExp,
|
|
414
|
+
cleanLatestRunId,
|
|
396
415
|
isSameTest,
|
|
397
416
|
fetchSourceCode,
|
|
398
417
|
fetchSourceCodeFromStackTrace,
|
package/src/xmlReader.js
CHANGED
|
@@ -217,6 +217,7 @@ class XmlReader {
|
|
|
217
217
|
if (test.example) r.example = test.example;
|
|
218
218
|
if (test.file) r.file = test.file;
|
|
219
219
|
r.create = true;
|
|
220
|
+
r.overwrite = true;
|
|
220
221
|
if (r.status === 'Passed') r.status = STATUS.PASSED;
|
|
221
222
|
if (r.status === 'Failed') r.status = STATUS.FAILED;
|
|
222
223
|
if (r.status === 'Skipped') r.status = STATUS.SKIPPED;
|
|
@@ -293,6 +294,7 @@ class XmlReader {
|
|
|
293
294
|
title,
|
|
294
295
|
suite_title,
|
|
295
296
|
run_time,
|
|
297
|
+
retry: false,
|
|
296
298
|
});
|
|
297
299
|
});
|
|
298
300
|
});
|
|
@@ -569,6 +571,7 @@ function reduceTestCases(prev, item) {
|
|
|
569
571
|
root_suite_id: TESTOMATIO_SUITE,
|
|
570
572
|
suite_title: suiteTitle,
|
|
571
573
|
files,
|
|
574
|
+
retry: false,
|
|
572
575
|
});
|
|
573
576
|
});
|
|
574
577
|
return prev;
|